ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE Fakulta elektrotechnická Katedra telekomunikační techniky
Asterisk Gateway Interface
srpen 2007
Diplomant: Vedoucí práce:
Ondřej Caletka Ing. Miloslav Brada
Prohlášení Prohlašuji, že jsem zadanou bakalářskou práci vypracoval sám, s přispěním vedoucího práce a používal jsem pouze literaturu v práci uvedenou. Dále prohlašuji, že nemám námitek proti půjčování, nebo zveřejňování mé bakalářské práce, nebo její části se souhlasem katedry.
V Praze dne 15.8.2007 ALL YOUR BASE ARE BELONG TO US ALL YOUR BASE ARE BELONG TO US ALL YOUR BASE ARE BELONG TO US
podpis diplomanta
(zadání)
Anotace Práce se věnuje softwarové pobočkové ústředně Asterisk. Jedná se o svobodný software, běžící pod operačními systémy UNIXového typu. Podrobně je probírána instalace a konfigurace ústředny s důrazem na technologie přenosu hlasu v IP sítích (VoIP). Pomocí rozhraní Asterisk Gateway Interface jsou nakonec realizovány dvě telefonní informační služby – služba přesného času a interaktivní kurzovní lístek.
Summary This work focuses on software private branch exchange (PBX) Asterisk. It is free software, running under UNIX-like operating systems. There is detailed focus on instalation and configuration of PBX with emphasis on voice over IP (VoIP) technologies. Using Asterisk Gateway Interface, two automated information services are projected – precise time and interactive exchange list.
Obsah 1.Úvod................................................................................................................................6 2.O Asterisku.....................................................................................................................6 3.Propojení Asterisku se stávající telefonní sítí.................................................................7 4.Propojení Asterisku s VoIP sítěmi..................................................................................8 5.Instalace Asterisku.......................................................................................................11 6.Konfigurace...................................................................................................................13 7.Číslovací plán................................................................................................................16 8.Spouštění a ovládání Asterisku....................................................................................21 9.Databáze AstDB............................................................................................................22 10.Asterisk Gateway Interface (AGI)...............................................................................23 11.Problematika hlasové syntézy....................................................................................26 11.1.Úvod.....................................................................................................................26 11.2.České číslovky v Asterisku..................................................................................27 11.3.Nahrávání hlasových nabídek.............................................................................29 11.4.Požadavky na mluvčího.......................................................................................33 12.Řešení aplikace „přesný čas“.....................................................................................35 12.1.Úvod.....................................................................................................................35 12.2.Popis realizace.....................................................................................................36 12.3.Kompilace a testování.........................................................................................38 13.Řešení aplikace „kurzovní lístek“...............................................................................40 13.1.Úvod.....................................................................................................................40 13.2.Popis realizace.....................................................................................................40 13.3.Testování a instalace...........................................................................................43 14.Závěr...........................................................................................................................44 15.Použitá literatura........................................................................................................45 16.Seznam příloh.............................................................................................................45
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
1. Úvod V tomto textu bude představena softwarová pobočková ústředna Asterisk. Důraz bude kladen především na technologie přenosu hlasu v IP sítích (VoIP). Budou představeny možnosti konfigurace ústředny, speciální databáze AstDB a rozhraní Asterisk Gateway Interface. V druhé části práce bude rozhraní Asterisk Gateway Interface využito pro praktickou realizaci dvou hlasových informačních systémů, přesného času a kurzovního lístku.
2. O Asterisku Asterisk (*) je svobodná pobočková ústředna nejen pro VoIP. Základní verzi naprogramoval Mark Spencer ve společnosti Digium, Inc., v roce 1999. Od té doby je díky otevřenému zdrojovému kódu a GNU GPL licenci projekt rozšiřován komunitou programátorů po celém světě. Tato licence mimo jiné umožňuje každému software používat k libovolnému účelu, a software modifikovat, což je umožněno zveřejněním zdrojových kódů. V případě distribuce je každý povinen distribuovat případné odvozené produkty pod stejnou licencí, tedy včetně zdrojových kódů. To zaručuje, že projekt nebude komerčně zneužit jako součást nějakého proprietárního řešení. Zveřejnění Asterisku je zásadním průlomem v oblasti telekomunikací, neboť jde vlastně o první svobodné řešení. Díky tomu, že svět telekomunikací se, narozdíl například od Internetu, rozvíjel nikoli akademicky, ale formou nejrůznějších státních zakázek, došlo k vytvoření mnoha, záměrně vzájemně nekompatibilních proprietárních řešení, jejichž primárním cílem často nebylo spojit účastníky, nýbrž vydělat co nejvíc peněz. Díky Asterisku má každý možnost „sáhnout si na ústřednu,“ postavit si s minimem nákladů PBX1 třeba doma, nebo celou ústřednu zredukovat pouze na převodník mezi různými telekomunikačními standardy. Asterisk je napsán v jazyce C, což umožňuje velkou portabilitu, kromě Linuxu pracuje i na všech ostatních POSIXových systémech a dokonce ke své činnosti nepo-
1 Private Branch Exchange – obecná zkratka pro soukromou pobočkovou ústřednu
-6-
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
třebuje ani PC, dá se nainstalovat například do RISC počítačů, jaké bývají uvnitř domácích broadband routerů. Anglické slovo Asterisk znamená hvězdičku (*), známý to znak jak z počítačů, tak i z tlačítkových telefonů. Znak hvězdička také na počítačích obvykle slouží jako tzv. wildcard (česky žolík), to znamená, že zastupuje všechny možné znaky (například v DOSu příkaz del *.*, v UNIXu rm -Rf *) Tento název vůbec není náhodný. Asterisk má totiž tolik funkcí a je tak flexibilní, že znak hvězdičky je na místě. Mezi ně patří: ●
Napojení
na stávající
telekomunikační
infrastrukturu
pomocí
T1/E1/J1
rozhraní, popřípadě analogových FXO a FXS portů ●
Podpora pro snad všechny existující VoIP protokoly s důrazem na SIP
●
Všechny běžné funkce PBX, jako identifikace volajícího, přepojování hovorů,
konferenční hovor ●
Pokročilé funkce PBX, jako například hlasová pošta, s možností zasílání e-
mailů s nahrávkou zprávy, parkování hovorů, volací fronty (funkce call-centra) ●
Podpora většiny kodeků: G.711a,u; G.725; GSM; iLBC; Speex a další
●
Bezešvé spojení libovolného kanálu s libovolným, automatické transkódování
hovorových dat
3. Propojení Asterisku se stávající telefonní sítí Aby bylo možné vůbec nějakou softwarovou ústřednu provozovat, je potřeba domluvit se se stávající, dnes převážně digitální telefonní sítí. Asterisk spolupracuje s adaptéry Zaptel, které vznikly jako produkt projektu The Zapata Telephony Project, vedeného Jimen Dixonem. Tento telekomunikční inženýr si kdysi povšimnul stále se zvyšujících rychlostí osobních počítačů, současně se snižováním jejich ceny. Všechny doposud vyrobené PC telefonní adapéry obsahovaly všemožné digitální signálové procesory, jejichž cena za MIPS2 byla mnohem vyšší, než cena za MIPS PC. Proto Jim Dixon pod výše uvedeným názvem uveřejnil „svobodný“ adaptér, který pro konverzi signálu nepoužívá žádný externí procesor, ale hlavní CPU počítače. Každý si tak, díky 2 Milion Instructions Per Second - Počet miliónů operací za sekundu.
-7-
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
otevřenému návrhu, může s naprostým minimem nákladů sestrojit vlastní ISA/PCI adaptér pro linky T1/E1/J1. Projekt
Zapata
nastartoval
podporu
PSTN3
adaptérů
v Asterisku.
Firma
Digium, Inc., která sponzoruje vývoj Asterisku, vyrábí a prodává celou řadu Zapata kompatibilních adaptérů, které na rozdíl od původního návrhu obvykle obsahují zabudovaný DSP potlačovač ozvěny, a jsou tedy vhodnější pro masové nasazení, ačkoli jsou několikanásobně dražší. Kromě linek T1/E1/J1 existují i adaptéry pro menší množství linek – digitální ISDN BRI a analogové FXO a FXS porty. ISDN BRI porty mohou být dvojího druhu. Buď jsou označeny TE (Terminal Equipment) a připojují se k nim ISDN telefony a ostatní terminály, nebo jsou označeny jako NT (Network Termintion) a připojují se k NT prvku a přes něj k ústředně. Zásadní rozdíl mezi režimy je v tom, která strana na kterém páru S/T rozhraní přijímá a vysílá, navíc v TE režimu by port měl poskytovat napájení pro připojené terminály. Podobná situace je u analogových linek. Porty se označují jako FXS (Foreign eXchange Station), pokud jsou určeny pro připojení koncových stanic, tedy nejčastěji telefonů. Takový port realizuje vlastně účastnickou sadu telefonní ústředny a poskytuje základní funkce BORSCHT4. FXO (Foreign eXchange Office) je oproti tomu určen pro připojení telefonní linky směrem k telefonní ústředně. Rozhraní tedy linku nenapájí, naopak napájení zakončuje a přijímá vyzváněcí napětí. Aby to nebylo tak jednoduché, tak při konfiguraci adaptéru se nekonfiguruje typ portu, ale typ signalizace, kterou port provádí. A protože názvy vychází z toho, k čemu má být port připojen, typ signalizace musí být opačný, tedy FXS na FXO portu a FXO na FXS portu.
4. Propojení Asterisku s VoIP sítěmi Význam slova Asterisk coby hvězdičky má své místo i podpoře VoIP sítí. Asterisk totiž podporuje všechny běžné VoIP protokoly, a je také jedinou VoIP ústřednou podporující oba proprietární protokoly dvou velkých hráčů na poli VoIP – Cisco a Nortel. 3 Public Service Telephone Network – Telefonní síť veřejné služby 4 Battery, Overvoltage protection, Ring, Supervision, Coding, Hybrid and Testing – napájení, přepěťová ochrana, vyzvánění, dohled, kódování, vidlice, test.
-8-
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Základním VoIP protokolem dnešních dní je SIP (Session Initiation Protocol). Tento protokol navrhlo IETF a uvedlo v roce 1999 jako RFC 2543. Dokumenty RFC se staly standardem Internetových protokolů, dílem jistě i proto, že každý dokument RFC je uvolněn jako public domain a je tedy volně přístupný každému, kdo jej chce implementovat. Protokol SIP je textový protokol, formálně podobný protokolům HTTP a SMTP, kterým se přenáší signalizace mezi účastníky. Standardně používá TCP a UDP port 5060. Klient udržuje SIP spojení se svým proxy (ústřednou) a prostřednictvím tohoto spojení volá ostatní klienty a získává požadavky (příkazy INVITE) od ostatních klientů. Po navázání spojení se vlastní hlas, či video, či online-hra (SIP je v tomto směru všestranný) přenáší prostřednictvím domluveného vysokého čísla UDP portu RTP (Real Time Protocol) protokolem přímo mezi dvěma koncovými body, bez asistence proxy. Tento scénář se obvykle jmenuje SIP lichoběžník a jeho příklad je na obrázku 4-1, včetně příkladu komunikace.
Obrázek 4-1: SIP lichoběžník
-9-
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Protokol SIP má některé nedostatky. Je tím především problém s průchodem překladači adres (NATy). Protokol totiž přenáší IP adresy počítačů a čísla portů v aplikační, 7. vrstvě modelu OSI, ovšem překladač adres může někde na cestě paketu IP adresu a číslo portu změnit; učiní tak pouze v hlavičce IP, na vrstvě 3, neboť vyšší vrstvy překladač adres nezkoumá. Zatímco vlastní SIP zprávy obvykle není problém přenést (pokud Proxy má veřejnou IP adresu), RTP cestu mezi klienty není možné navázat. Tomuto problému je možné čelit několika způsoby: ●
Použít pomocný server s protokolem STUN (RFC 3489). Tento protokol
pomůže klientovi otevřít UDP spojení z Internetu směrem ke klientovi a tím vytvořít RTP spojení; dále se na hovoru nepodílí. ●
Přinutit překladač adres, aby určitá čísla portů směroval určitým klientům
a nakonfigurovat klienty, aby jako svou IP adresu v protokolu SIP uváděli uživatelem stanovenou veřejnou adresu a aby čísla portů volili z uživatelem definovaného rozsahu. ●
Používat i pro RTP spojení prostředníka, obvykle přímo jako součást ústředny.
To s sebou nese nevýhody, například při nevhodném umístění ústředny a klientů, ale zároveň je to jediná možnost, jak v ústředně hovory nahrávat, přijímat DTMF signály, nebo transkódovat různé kodeky mezi sebou. Proto tuto cestu volí Asterisk a standardně většina hovorů prochazí přes něj. Dalším problémem protokolu SIP mohou být DoS útoky, znemožňující činnosti ústředen a také šíření nevyžádaných inzertních hovorů, hovorového spamu. Vzhledem k tomu, že většina současných VoIP instalací je nastavena tak, aby se kdokoli zdarma přes Internet dovolal, může být dost nepříjemné i pouhé odeslání příkazu INVITE na všechny pobočky, při kterém se po celé budově rozezvoní telefony. Dalším hlavním protokolem, podporovaným Asteriskem je protokol IAX (Inter Asterisk eXchange), v současné době ve verzi 2. Protokol IAX vyvinul opět Mark Spencer ve společnosti Digium, jeho původní účel bylo jednoduché a přitom efektivní vytvoření příček (trunk) mezi dvěma ústřednami Asterisk. Standardně používá UDP port 4569. Proti SIPu jde o binární protokol, který v jednom datovém proudu kombinuje - 10 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
jak signalizační, tak hovorová data. V režimu příčky jsou hovorová data od všech kanálů sdružena do jednoho spojení a používají společnou signalizaci. Díky tomu má protokol oproti SIPu výrazně menší datovou redundanci (overhead) a po stejné kapacitě linky údajně dokáže přenést o několik desítek procet víc hovorů. Díky používání jednoho UDP portu sice na jednu stranu odpadají problémy s překladači adres, na druhou stanu hovorové spojení musí nutně jít do stejného směru co signalizace. Vzhledem k tomu, že toto se většinou používá i se SIPem, stal se IAX oblíbeným protokolem i pro připojení koncových zařízení. Pro průchod firewally tak stačí povolit pouze jediný port, není třeba sledovat dynamicky domlouvaná RTP spojení. V počátcích
IP
telefonie
se jako
protokol
používal
protokol
mezinárodní
telekomunikační unie ITU H.323. Tento binární protokol také přenáší IP adresy a čísla portů v aplikační vrstvě, tyto problémy jsou ještě umocněny uzavřeností protokolu (ITU doporučení se prodávají, nedají se získat bezplatně). Proto je v poslední době tento protokol víceméně na ústupu. Přesto spousta telefonních ústředen tento protokol používá.
Asterisk
podporuje
H.323
dokonce
dvěma
moduly,
jednak
původní
chan_h323.so, jednak chan_oh323.so, produkt projektu OpenH323, který má zájem o svobodnou implementaci protokolu H.323. Protokol MGCP (Media Gateway Control Protocol) je další protokol definovaný IETF, konkrétně RFC 3435. Tento protokol se snaží konstruovat co nejjednodušší koncová zařízení a veškeré výkony řeší server. Asterisk se umí chovat jako brána pro takové telefony, neumí však pracovat jako koncové zařízení MGCP. Tento protokol je v současné době na ústupu před SIP a IAX a jeho použití pro nové instalace se nedoporučuje. Dále Asterisk alespoň částečně podporuje proprietární protokoly Cisco Skinny/SCCP a Nortel UNISTIM.
5. Instalace Asterisku Preferovaná cesta instalace Asterisku je kompilace ze zdrojových kódů; nicméně existují i binární balíčky pro různé Linuxové distribuce, které obvykle obsahují navíc i několik neoficiálních záplat. Na jiných systémech jsem Asterisk instalovat nezkoušel.
- 11 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
V době psaní tohoto dokumentu právě vyšla nová verze 1.4.0. Protože nejsem vázán zachováním funkčnosti, budu pro účely tohoto dokumentu popisovat verzi 1.4.0, není-li uvedeno jinak. Zásadní změna verze 1.4.0 tkví již ve způsobu instalace. Dříve se Asterisk instaloval pouze rozbalením zdrojových kódů a spuštěním příkazů make && make install. Verze 1.4.0 začala používat nástroj GNU Autoconf, nicméně postup a výsledek kompilace se stále poněkud liší od tradiční „svaté trojice“ ./configure && make && su -c 'make install'. Na rozdíl od GNU nástrojů se Asterisk standardně neinstaluje do podstromu /usr/local, nýbrž přímo do hlavního stromu systému podobně, jako ostatní balíky distribuce. Navíc je možno před kompilací (po konfiguraci) příkazem make menuselect upravit konfigurační možnosti podobně jako při kompilaci jádra Linuxu. Po instalaci se pro první konfiguraci vyplatí příkazem make samples nechat vyrobit vzorové konfigurační soubory, aby bylo od čeho začít. Pojďme si projít adresáře, kam se Asterisk nainstaloval: ●
Spustitelné soubory jsou umístěny ve standardním adresáři /usr/sbin
●
Konfigurace je uložena v adresáři /etc/asterisk, každý modul zde má svůj
konfigurační soubor obvykle pod svým jménem ●
Dalším důležitým adresářem je /usr/lib/asterisk. Zde jsou umístěny všechny
moduly, které se do Asterisku nahrávají za běhu. Moduly mají formát dynamické knihovny a tedy příponu .so (shared object) ●
Data,
se kterými
program
pracuje
za běhu,
jsou
uložena
v cestě
/var/lib/asterisk. Jde například o databázi AstDB, spustitelné programy AGI, veřejné klíče serverů pro autentizaci, zvuky, které ústředna produkuje, nebo hudbu, kterou Asterisk přehrává při přepojování hovorů. ●
V adresáři /var/spool/asterisk je několik podadresářů, jejichž obsah Asterisk
za běhu trvale sleduje. Díky tomu je možno uskutečnit hovor pouhým umístěním vhodného souboru do vhodného adresáře. ●
Protokoly o tom, co se v ústředně děje, jsou ukládány do /var/log/asterisk.
Zvláštní úlohu má podadresář cdr-csv, kam se ukládají tzv. CDR záznamy (Call
- 12 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Detail Record), které slouží pro případné účtování a jejich ukládání je tedy vždy nesmírně důležité.
6. Konfigurace Jak jsem již uvedl, konfigurační soubory bydlí v adresáři /etc/asterisk. Pokud po instalaci spustíme make samples, bude tento adresář naplněn vzorovými konfiguračními soubory, které můžeme použít pro rychlé spuštění ústředny. Pojďme si popsat ty nejdůležítější z nich: ●
V souboru asterisk.conf jsou nastaveny cesty jednotlivých částí Asterisku, tak
jak je popsáno v předchozí kapitole ●
Soubor sip.conf konfiguruje modul pro SIP protokol. V tomto souboru
se nachází definice všech spojení prostřednictvím protokolu SIP, to znamená všechny SIP pobočky a všechny SIP příčky ●
Obdobně fungují soubory iax2.conf a h323.conf pro tyto protokoly
●
Zvláštní význam má soubor alsa.conf, resp. oss.conf. V těchto souborech
se konfiguruje ovladač, který vytvoří telefonní kanál z běžné zvukové karty počítače, na kterém běží Asterisk. Kromě využití k odlaďování číslovacího plánu („telefon“ se ovládá z konzole Asterisku) má i celkem perspektivní využití pro připojení vnitřního rozhlasu organizace. Rozhraní je možné nakonfigurovat, aby automaticky vyzvedávalo příchozí hovory, stačí tedy vyřešit zapnutí a vypnutí rozhlasové ústředny – například vhodným AGI skriptem ●
Soubor modules.conf ovlivňuje načítání modulů do Asterisku. Například není
možné zároveň používat chan_oss.so a chan_alsa.so. Proto je jeden z těchto dvou modulů v tomto souboru zablokován. ●
Snad nejdůležítějším konfiguračním souborem je extensions.conf. Tento
soubor v sobě skrývá číslovací plán, což je vlastně předpis toho, co se má stát při vytočení toho kterého čísla Všechny konfigurační soubrory mají stejný formát, vzdáleně podobný INI-souborům z MS Windows. Dělí se na sekce, každá sekce je uvozena názvem těsně uzavřeným v hranatých závorkách na samostatném řádku. Jednotlivé proměnné se nastavují - 13 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
výrazem proměnná = hodnota na samostatném řádku, přičemž za proměnnou a před hodnotu může a nemusí být mezera. Komentáře se, na rozdíl od ostatních UNIXových programů, zapisují za středník. Důvod je prostý – znak mřížky se vyskytuje na klávesnici telefonu a tak je velice pravděpodobně, že tento znak bude tvořit obsah konfigurace. Kromě přiřazovacích příkazů existují i výrazy, které se podobají operátoru zpřístupnění z jazyka C++: klíčové slovo => funkce. Tyto výrazy se často používají například v číslovacím plánu. Začněme jednoduchým příkladem konfiguračního souboru sip.conf: ;sip.conf – konfigurace sip klientu [general] context = default bindaddr = 0.0.0.0 bindport = 5060 ;srvlookup = yes ; povolit volani sip:
[email protected] ;nastaveni preference kodeku disallow = all ; vsechny zakazeme allow = alaw ; a povolime tyto 3 allow = ulaw allow = gsm ;registrace u SIP providera ;register => astjmeno:astheslo@operator ;povol smerovani RTP optimalni cestou canreinvite=nonat ;aktivace Jitter Bufferu jbenable=yes ;nasleduje definice jednotlivych klientu ;-------;VoIP operator pro prichozi hovory ;[operator] ;type=user ;host=sip.operator.cz ;context=incoming ;insecure=port,invite ; zakazat overovani identity operatora ;SIP telefon, linka 11 [11] - 14 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
type=friend context=internal callerid=Linka 11 <11> nat=no host=dynamic username=11 [12] type=friend context=internal callerid=Linka 12 <12> nat=no host=dynamic username=12 secret=12 Pojďme si konfigurační soubor projít. Začíná sekcí general, ve které se nastaví globální proměnné modulu, jako IP adresa a port, na který se má modul připojit. Dále jsou nastaveny kodeky, které chceme akceptovat. Pokud chceme zamezit použití extrémně úsporných kodeků (které jsou obvykle i extrémně nekvalitní), můžeme použít zde uvedený postup, kdy nejprve všechny kodeky zakážeme, a explicitně povolíme nekomprimované PCM kodeky G711.a (Alaw), který je nativní pro evropské digitální systémy, G711.μ (μlaw), americkou variantu předchozího, a GSM kodek. Pokud se chceme přihlásit k vzdálené ústředně (připojení VoIP PBX do veřejné sítě přes SIP) použijeme výraz register, jak je uvedeno v příkladu. Zvláštní význam má volba canreinvite, kterou lze blíže upřesnit u každého klienta. Pokud je volba reinvite povolena (yes), snaží se asterisk v momentě, kdy nemusí stát v cestě, efektivněji navázat RTP spojení, tedy přímo mezi koncovými zařízeními. Bohužel, některá koncová zařízení jsou známá tím, že nezvládnou přijmout příkaz reinvite. Navíc je zde již zmíněný problém s překladači adres. Takový problém se projeví tak, že jedna nebo druhá strana spojení neslyší. V takovém případě je třeba RTP spojení vést přes ústřednu, což zařídíme zakázáním této volby. Kompromisní varianta nonat zakazuje tuto funkci pouze pro klienty za překladači adres. Novou funkcí verze 1.4.0 je vyrovnávací paměť zpoždění (jitter buffer), volba jbenable ho povolí. Tato paměť dokáže znovu seřadit přícházející pakety hovoru pokud dorazí v jiném pořadí, než byly doručeny.
- 15 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Po globální sekci následuje definice jednotlivých klientů. Rozlišujeme celkem tři typy klientů: user, peer a friend. Typ user je takový, od kterého ústředna přijímá volání. Volání se směrují k klientům typu peer. Pokud chceme od klienta jak hovory přijímat, tak k němu hovory směrovat, použijeme typ friend, který vlastně zastupuje dvě definice téhož klienta jednou jako user a podruhé jako peer. Pro klienty typu user a friend je třeba nastavit kontext číslovacího plánu, do kterého spadnou hovory od těchto klientů. Dále je možné proměnnou callerid vynutit CLIP5 informaci nezávisle na nastavení koncového zařízení. Volba nat určuje, zda je mezi klientem a ústřednou překladač adres. Pokud je nastavena na ano (yes), Asterisk jednak zabrání přímému propojení RTP protokolu, navíc nebude příliš důvěřovat IP adresám a číslům portů v SIP zprávách (které obvykle projdou NATem bez povšimnutí) a raději se podívá po adresách v hlavičkách IP protokolu (ty NAT upravuje). Volba host nastavuje IP adresu klienta. Používá se zejména v případech, kdy klientem je nadřazená ústředna, ke které se registrujeme příkazem register. Pokud chceme umožnit „roaming“ poboček, použijeme host=dynamic, a přinutíme každou pobočku, aby se přihlásila svým jménem a heslem. To se definuje klíči username a secret. Tím jsme úspěšně definovali dvě pobočky.
7. Číslovací plán Číslovací plán je vlastně program, podle kterého ústředna spojuje hovory. V případě asterisku se jedná o konfugurační soubor extensions.conf, což je klasický kofigurační soubor. Kromě toho nově Asterisk podporuje vlastní programovací jazyk Asterisk Extension Language, v současnosti ve verzi 2.0, v souboru extensions.ael. Pojďme si jednoduchý číslovací plán představit: [internal] exten => 11,1,Answer() exten => 11,n,Dial(SIP/11) exten => 11,n,Hangup() exten => 12,1,Dial(SIP/12) include => funkce 5 Caller Identification Presentation – prezentace čísla volajícího
- 16 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
[funkce] exten => exten => exten => exten => exten =>
90,1,Answer() 90,n,Wait(1) 90,n,Playback(demo-echotest) 90,n,Echo() 90,n,Hangup()
exten exten exten exten exten exten exten
91,1,Answer() 91,n,Wait(1) 91,n,Playback(dictate/record) 91,n,Record(/tmp/echotmp:wav,2,4,) 91,n,Playback(dictate/playback) 91,n,Playback(/tmp/echotmp) 91,n,Hangup()
=> => => => => => =>
Asterisk Gateway Interface
[incoming] exten => s,1,Dial(SIP/11&SIP/12) Číslovací plán se sestává z kontextů, které jsou v hranatých závorkách. Základní příkaz je exten, zkratka slova extension, pobočka. Základní formát je číslo pobočky, priorita a aplikace, oddělené čárkou. Číslo pobočky je číslo, které musíme vytočit, abychom se do daného místa číslovacího plánu dostali. Priorita určuje pořadí, v jakém budou příkazy se stejným číslem pobočky vykonávány. A nakonec aplikace, což je vlastně příkaz, který se má provést. Jeho parametry jsou v závorce za jeho jménem. Ač by se mohlo zdát, že jednotlivé příkazy mohou být libovolně seřazeny, není tomu tak. Asterisk je citlivý na správné pořadí a na správné očíslování priorit. Ty musí vždy začínat číslem 1 a žádná nesmí chybět – chybějící priorita způsobí ukončení hovoru s chybovou zprávou na konzoli asterisku. Abychom si zachovali konzistentní číslování i po případných úpravách plánu, můžeme označit pouze první položku plánu a ostatní položky označit číslem n, což zkracuje next, další. Pojďme si projít předchozí příklad. Nejprve definujeme kontext internal, pro vnitřní linky. Při vytočení čísla pobočky hovor nejprve vyzvedneme (Answer), poté zavoláme příslušnou pobočku (Dial) a nakonec zavěsíme. Pro pobočku číslo 12 je použit jednodušší zápis pouze aplikace Dial. Poslední řádek kontextu internal do něj vkládá kontext funkce, ve kterých jsou nadefinovány dvě základní funkce, známé jako Echo testy. První funkce na čísle 90 linku nejprve vyzvedne, poté přehraje volajícímu informační zprávu (Playback) a nakonec aktivuje funkci Echo, která veškerá přijatá - 17 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
hlasová data okamžitě vrací zpět volajícímu, který si tak může udělat představu o kvalitě spojení mezi sebou a ústřednou. Druhý program na pobočce 91 po vyzvednutí linky přehraje informační zprávu, poté nahraje 4 sekundy z linky, které nakonec přehraje volajícímu zpět. Tento program je vhodný pouze po účely jednoduchého předvedení číslovacího plánu, do produkčního prostředí se nehodí zejména proto, že neošetřuje obsluhu více než jednoho hovoru – v takovém případě by totiž další hovor přepisoval záznam prvního hovoru. Abychom takovou situaci ošetřili, můžeme buď použít kanálové proměnné, které nám jednotlivé hovory rozliší, nebo celý problém řešit mimo ústřednu externím AGI skriptem. Za zmíňku stojí ještě jeden speciální název pobočky s, coby start. Tato pobočka se spustí v momentě, kdy do kontextu spadnul hovor, aniž by bylo vytočeno nějaké číslo. To nastává typicky v kontextu pro příchozí hovory. U takového hovoru můžeme v našem případě například zavolat obě pobočky s tím, že hovor spojíme na tu, která hovor vyzvedne první. Kromě pevně definovaného plánu je možné využít proměnné a tím jednak ušetřit psaní, jednak zvýšit přehlednost a v neposlední řadě i přidat novou funkčnost do číslovacího a proměnné
plánu. prostředí.
Rozlišujeme
3 druhy
Na proměnné
proměnných
se odkazujeme
– globální, kanálové
podobně
jako
v BASHi:
${promenna}. Globální proměnné jsou definovány ve speciálním kontextu globals na začátku souboru extensions.conf.
Také
je
možné
globální
proměnnou
definovat
aplikací
Set
s parametrem g (global; v minulosti byl pro tento účel příkaz SetGlobalVar). Tato proměnná platí v celém plánu, po celou dobu činnosti ústředny. Pomocí globálních proměnných můžeme například nadefinovat vztah mezi fyzickými porty ústředny a logickými linkami. Kanálové proměnné mají platnost pouze pro ten který konktrétní hovor. Některé jsou automaticky definovány, jako například CallerID, jiné můžeme definovat opět aplikací Set.
- 18 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Konečně proměnné prostředí jsou standardní UNIXové proměnné prostředí, které zdědí Asterisk při spuštění. K těmto proměnným se přistupuje speciální referencí ${ENV(PROMENNA)}6. Pro definici číslovacího plánu je občas potřeba obsáhnout jedním pravidlem více volaných čísel. Asterisk používá pro takovýto účel výrazy vzdáleně podobné regulárním výrazům. Každý takový výraz začíná znakem _ (podtržítko), těsně následují buď čísla, nebo znaky X - zastoupí číslici 0 až 9, Z - zastoupí číslici 1 až 9, N - zastoupí číslice 2 až 9 (pro Česko se moc nehodí). Je také možno v hranatých závorkách vyjmenovat povolené číslice, popřípadě rozsah oddělený znakem mínus - např [16-8] zastoupí číslice 1, 6, 7, nebo 8. Nakonec znak . (tečka) vyhoví pro jednu nebo víc libovolných znaků. Pokud chceme následně v plánu zjistit, jaké že číslo bylo skutečně vytočeno, můžeme použít kanálovou proměnnou EXTEN. Pěkně to ilustruje následující příklad: exten => _0.,1,Answer() exten => _0.,n,SayDigits(${EXTEN}) exten => _0.,n,Hangup() Tento příklad všechna volání na čísla začínající nulou vyzvedne, přečte volajícímu volané číslo a zavěsí. Číslovací plán dokáže také vyhodnocovat jednoduché výrazy. Syntax je $[výraz]. Je však důležité vědět, že není použit nijak sofistikovaný algoritmus pro načítání výrazů a je proto potřeba operátory od výrazů oddělovat mezerami, jinak je celý výraz vnímán jako jeden řetězec. Akceptovány jsou porovnávací operátory =, >, >=, <, <=, !=. Tyto operátory porovnávají buď řetězce7, nebo celá čísla, pokud jsou oba argumenty celočíselné. Výstupem porovnání je buď číslice 1, pokud je výraz pravdivý, nebo 0 pokud je nepravdivý. Dále mohou být použity logické operátory & pro log. součin, | pro log. součet, kde pravdivý je každý výraz různý od prázdného řetězce a nuly. Funkce součinu vrací při pozitivním výsledku levý argument, jinak nulu, funkce součtu vrací při pravdě levý argument, při nepravdě pravý.
6 Toto je vlastně přístup k funkci ENV číslovacího plánu, jak poznáme dále. 7 Porovnání probíhá v souladu s nastavením lokalizace prostředí, ve kterém je Asterisk spuštěn, neměl by být tedy problém řadit ani české řetězce.
- 19 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Celočíselné operandy je možno sčítat (+), odečítat (-), násobit (*) a celočíselně dělit (/) se zbytkem (%). Velice zajímavá je podpora opravdových regulárních výrazů, pomocí operátoru : (dvojtečka), kde na levé straně stojí zdrojový výraz a na pravé regulární výraz; ten je vždy ukotven k začátku výrazu implictním znakem ^. Pokud regulární výraz neobsahuje podvýrazy (v kulatých závorkách \( \)), je výstupem počet znaků levého výrazu, které vyhověly; pokud nevyhoví ani jeden, vrátí se 0. Obsahuje-li regulární výraz alespoň podvýraz, je výstupem obsah prvního podvýrazu (ekvivalent \1); pokud výraz nevyhoví, vrátí se prázdný řetězec. Poměrně novou vlastností Asterisku od verze 1.2, jsou funkce číslovacího plánu. Formálně jde vlastně o proměnné, které jsou následovány kulatými závorkami s případnými parametry. Protože jde o proměnné, je možné je nejen číst, ale i do nich zapisovat aplikací Set. Funkce číslovacího plánu nahrazují činnost některých aplikací se stejnou funkcí, tyto aplikace jsou navrženy na odstranění a neměly bychom je používat. Ukažme si pro ilustraci jednoduchý předpis, který volajícímu oznámí, kolik číslic vytočil: exten => _0.,n,SayNumber(${LEN(${EXTEN})}) ^ ^ ^ ^ ^^^^ 1 2 3 4 4321 Abychom pochopili tuto na první pohled nesmyslnou změť závorek, očísloval jsem je. Rozeberme si tento příkaz z venku dovnitř. Zavoláme aplikaci SayNumber, která vysloví číslo, které jí předáme jako argument v závorkách (1). Číslo, které chceme vyslovit je funkcí číslovacího plánu a protože ta se chová jako proměnná, musíme jí zpřístupnit pomocí ${} (2). Následuje název funkce LEN, za níž v kulatých závorkách (3) funkci předáme řetězec, jehož délku chceme zjistit. Protože tento řetězec je kanálová proměnná, musíme ji zpřístupnit opět pomocí ${} (4). Druhý příklad, kde nastavíme jméno volajícího, bude o dost přehlednější: exten => _0.,n,Set(CALLERID(name)="Josef Novak") Aby byl úvod do číslovacího plánu úplný, zbývá probrat skok. Základní nepodmíněný skok se provádí příkazem Goto s jedním, dvěma, nebo třemi argumenty, oddělenými čárkami. Pokud jsou zadány všechny tři, první určuje cílový kontext, druhý číslo pobočky a třetí prioritu, na kterou skočit. Pokud jsou zadány dva, skáče se ve stejném - 20 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
kontextu na číslo pobočky a prioritu. A pokud je zadán jen jeden, skáče se na danou prioritu, ve stejném kontextu a pobočce. Podmíněný skok GotoIf obsahuje navíc podmínku, na základě které rozhoduje, kam skočit. Ukažme si jeho použití, na funkci, která bude počítat od 10 do 1 a pak zavěsí. exten exten exten exten exten exten exten
=> => => => => => =>
93,1,Answer() ; vyzvedneme hovor 93,2,Set(cislo=10) ; nastavíme číslo 93,3,SayNumber(${cislo}) ; přečteme ho 93,4,Set(cislo=$[${cislo} - 1]) ; odečteme 1 93,5,Wait(0.5) ; počkáme 93,6,GotoIf($[${cislo} > 0] ? 3 : 99) ; podmíněný skok 93,99,Hangup() ; zavěsíme
Jak je vidět, syntaxe příkazu GotoIf je podobná podmiňovacímu výrazu z jazyka C. Jedno z čísel je možné vynechat, pak se namísto skoku pokračuje další prioritou.
8. Spouštění a ovládání Asterisku Nyní již máme dostatek informací k tomu, abychom mohli Asterisk poprvé spustit. Standardně je vše připraveno k tomu, aby byl Asterisk spouštěn jako superuživatel root. To s sebou ovšem nese poměrně značná bezpečnostní rizika, neboť případný útočník
při
zneužití
chyby
může
získat
přístup
na celý
server
a například
překonfigurovat ústřednu tak aby neúčtovala, popřípadě účtovala někomu jinému. Proto existují postupy, jak spouštět Asterisk jako neprivilegovaný uživatel a dokonce znemožnit tomuto uživateli i zápis do konfiguračních souborů (což samozřejmě brání v aktualizaci konfigurace Asteriskem). Pro naše testovací účely však můžeme spustit příkaz asterisk jako root. Pokud ho spustíme bez parametrů, přejde do pozadí. Pokud přidáme parametr -c (console), zůstane v popředí a z terminálu se stane systémová konzole. Další možností je spustit asterisk s parametrem -r (remote) a tak si připojit konzoli k už běžícímu Asterisku na pozadí. Rozhraní příkazového řádku má vestavěnou nápovědu pod příkazem help. Verze 1.4.0 udělala v příkazech pořádek, takže každý příkaz začíná názvem modulu, kterého se týká. Staré formy příkazů jsou zatím podporovány, ale při jejich použití se objeví upozornění, že budou brzy zrušeny.
- 21 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Základním příkazem je vypnutí ústředny, k tomu slouží příkaz stop now. Chceme-li, aby probíhající hovory nebyly násilně ukončeny, použijeme příkaz stop gracefully, ústředna přestane přijímat nová volání, ale počká, až stávající volání skončí. Máme-li nakonfigurován kanálový ovladač ALSA nebo OSS, můžeme pomocí konzole uskutečňovat spojení a jednoduše tak testovat nastavení. K tomu použijeme příkaz console dial <číslo>, pro vytočení čísla a console hangup pro zavěšení. Změníme-li číslovací plán, či ostatní konfigurační soubory, můžeme jeho obsah za běhu ústředny znovu načíst příkazem dialplan reload, resp. reload pro načtení všech souborů. Jak ukazují zkušenosti na Internetových fórech, není vhodné nechávat Asterisk běžet trvale a často ho reloadovat. Lepší je poměrně často (např. 1x denně) celou ústřednu restartovat. K opravdovému restartu je třeba používat příkaz stop, protože od verze 1.2.0 je příkaz restart implementován stejně jako reload.
9. Databáze AstDB Máme-li již funkční zákládní instalaci, můžeme se pustit do pokročilejších vlastností pobočkové
ústředny
Asterisk.
Začněme
databází
AstDB.
Jedná
se o databázi
integrovanou do číslovacího plánu. Do databáze můžeme ukládat důležité informace o zákazníkovi, jako například výši volacího kreditu, osobní nastavení každé pobočky (čas před přesměrováním do hlasové pošty, CallerID...). Výkonné jádro této databáze tvoří poměrně historická nerelační databáze Berkley DB verze 1. Hlavním důvodem je BSD licence, pod kterou je tato databáze vydána. Její začlenění do projektu Asterisk tak nepřidává komplikace s další, ne vždy zcela slučitelnou, licencí. Fakt, že součástí Asterisku je takto stará databáze, je také jedním z důvodů vzniku projektu OpenPBX, což je fork Asterisku, který chce tak svobodný program jako je Asterisk ještě osvobodit od společnosti Digium. AstDB obsahuje rodiny (family), ve kterých jsou klíče (key) a hodnoty (value). Uvnitř rodiny smí být každý klíč jedinečný. Pro přístup k databázi z číslovacího plánu se používá funkce číslovacího plánu DB, které se jako parametr předává jméno rodiny a klíče, oddělené lomítkem. Pro zápis do databáze tak můžeme použít aplikaci Set, pro čtení operátor zpřístupnění
- 22 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
proměnné. Pokud chceme nějaký klíč z databáze odstranit, použijeme aplikaci DBdel(rodina/klíč). Aplikace DBdeltree(rodina) odstraní celou rodinu z databáze. K databázi můžeme také přistupovat z konzole ústředny. Příkazy začínají klíčovým slovem database. Databázi si můžeme předvést na tomto příkladu. Povšimněme si také nového způsobu číslování priorit, kde pro klíčové body můžeme použít textová návěstí. exten exten exten exten exten exten exten
=> => => => => => =>
92,1,Answer() 92,n(loop),Set(pocet=${DB(pokus/pocet)}) 92,n,GotoIf(${pocet}?:init) 92,n,SayNumber(${pocet}) 92,n,Set(pocet=$[${pocet} + 1]) 92,n,Set(DB(pokus/pocet)=${pocet}) 92,n,Goto(loop)
;v pripade, ze klic v DB neni, skoci se sem exten => 92,n(init),Set(DB(pokus/pocet)=0) exten => 92,n,Hangup() Po vyzvednutí linky se pokusíme z databáze načíst z rodiny pokus klíč pocet. Pokud se to nezdaří, v proměnné pocet bude prázdný řetězec. V takovém případě poskočíme na návěstí init, kde do databáze uložíme počáteční hodnotu a zavěsíme. Při dalším zavolání už z databáze načteme hodnotu, přečteme jí, přičteme jedničku, uložíme zpět a celý postup opakujeme. Kdykoli během hovoru zavěsíme, počítání se zastaví, ale po novém zavolání pokračuje od stejného místa. Klíč posléze z databáze odstraníme zadáním database del pokus pocet, popřípadě database deltree pokus.
10. Asterisk Gateway Interface (AGI) AGI je rozhraní, pomocí kterého může Asterisk volat jiné programy a předat jim tak řízení hovoru. Tvůrci zvolili velmi jednoduchý a přitom účinný model komunikace s externímy programy. Tím je propojení přes standardní popisovače souborů číslo 0,1 a 2, tedy standardní vstup, výstup a chybový výstup. Vzhledem k tomu, že práci se soubory, resp. se standardnímy vstupy a výstupy implementuje snad každý existující programovací jazyk, je rozhraní AGI velmi flexibilní. Pomocí AGI můžeme dokonce vyrobit celý číslovací plán, pokud nám je nějaký programovací jazyk bližší, než nativní jazyk číslovacího plánu. Nicméně původním účelem AGI je mít možnost ovlivnit hovor - 23 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
vnějšími vlivy a poskytovat tak automatizované informace o teplotě, čase, kurzovních lístcích, atd. Proto se nám vyplatí používat programovací jazyk blízký zdroji informace, tedy například PHP pro načítání informací z Webu. AGI skript se spouští z číslovacího plánu aplikací AGI(), jako parametr se předává jméno skriptu, relativně k adresáři /var/lib/asterisk/agi-bin. Asterisk program spustí a na standardní vstup programu pošle všechny proměnné, které má skript k dispozici. Poté odešle prázdný řádek a čeká na příkazy od skriptu. Skript na svůj standardní výstup odešle příkaz a čeká na odpověď. Asterisk odpoví a opět čeká na příkaz. To se opakuje až do konce skriptu. Navíc může skript odeslat informace o své činnosti na standardní chybový výstup. Taková informace se zobrazí na konzoli Asterisku. Bohužel i ve verzi 1.4.0 trvá jistý problém. Díky němu se chybový výstup z AGI promítá pouze na první konzoli Asterisku, nikoli na všechny. Kromě AGI existuje ještě několik podalternativ: EAGI, které na popisovači souboru 3 poskytuje skriptu zvukový proud; DeadAGI, ktery pracuje i na zavěšených kanálech a FastAGI, který dovoluje spouštět AGI skripty přes síť. Nicméně základní funkčnost je pro všechny stejná. Důležité však je, aby spouštěný skript neukládal výstup do vyrovnávací paměti. Jinak by mohlo dojít k tomu, že skript odešle příkaz, bude čekat na odpověď, ale Asterisk bude stále čekat na příkaz, což vede do nekonečné smyčky. Činnost AGI si můžeme nejlépe předvést na tomto jednoduchém příkladě, napsaném přímo v shellu BASH. Soubor se jmenuje agidemo.agi #!/bin/bash # Skript pro demonstraci Asterisk Gateway Interface echo AGI demo 1>&2 # nacteni promennych read AGI while [ "$AGI" != "" ]; do # vypis na konzoli echo $AGI 1>&2 read AGI done # promenne nacteny # ahoj svete echo "STREAM FILE hello-world ''" - 24 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
read result echo $result 1>&2 Co tento skript dělá? Po spuštění nejprve vypíše na konzoli text AGI demo. Poté ve smyčce načte všechny proměnné, až do prázdného řádku. Pro proměnné zatím nemáme využití, vypíšeme je tedy pouze na konzoli. Potom pošleme příkaz STREAM FILE, který je obdobou aplikace číslovacího plánu Playback(). Je důležité, že tento příkaz má dva povinné argumenty - název souboru a seznam číslic, které mohou zvuk přerušit - a tyto dva argumenty je v každém případě třeba předat. Proto jsou za prvním argumetem dvě jednoduché uvozovky, které označují prázdný druhý argument. Po odeslání příkazu příkaz read načte výsledek příkazu. Tento výsledek poté odešleme na standardní chybový výstup, tedy na konzoli Asterisku. Po spuštění se na konzoli asteriku objeví následující: AGI demo agi_request: agidemo.agi agi_channel: ALSA/hw:0,0 agi_language: en agi_type: Console agi_uniqueid: 1168286965.0 agi_callerid: unknown agi_calleridname: unknown agi_callingpres: 0 agi_callingani2: 0 agi_callington: 0 agi_callingtns: 0 agi_dnid: unknown agi_rdnis: unknown agi_context: internal agi_extension: 94 agi_priority: 1 agi_enhanced: 0.0 agi_accountcode: 200 result=0 endpos=12531 Z výpisu je vidět, jaké proměnné jsou AGI skriptu předávány. Také je vidět, že výsledek příkazu, zřejmě po vzoru HTTP protokolu, začíná číslem 200, poté následuje výraz result, který oznamuje výsledek operace. Nula v tomto případě znamená normální konec.
- 25 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
11. Problematika hlasové syntézy 11.1. Úvod Pro realizaci hlasových informačních systémů je nezbytné zabývat se otázkou hlasové syntézy. Systémy hlasové syntézy můžeme rozdělit do několika skupin: 1. Systémy bez lidského hlasu a) Formantová syntéza – k vytváření hlasu se používá akustický model. Jedná se o nejjednodušší systémy s výrazně „robotickým“ hlasem, které však jsou díky jednoduchosti schopny pracovat i na 8-bitových počítačích. b) Artikulační syntéza – k vytváření hlasu je použito fyzikálního modelu řečového ústrojí. Tento princip by měl produkovat „lidštější“ výstup, nicméně není v praxi hojně rozšířen. 2. Systémy s předem nahraným lidským hlasem (konkatenační) a) Difónová syntéza – nahraná slova jsou rozstříhána na jednotlivé hlasové entity. Obvykle se jedná o dvojice hlásek (difóny), neb výslovnost každé hlásky je ovlivněna okolními. Takových difónů je v jazyce nejvýše 500, jedná se tedy o relativně malou databázi. Toto je v současnosti asi nejrozšířenější způsob syntézy hlasu systémem TTS. b) Výběrová (unit-selection) syntéza – nahrané věty jsou rozstříhány jak na hlásky a slabiky, tak i na slova, slovní spojení a celé věty. Systém pak samočinně podle vstupního textu vybírá nejvhodnější nahrané entity, tvoříce hlas údajně k nepoznání od skutečně lidského. Nevýhodou tohoto systému je kromě složitého softwaru také vysoká náročnost na mluvčího – je třeba nahrát desítky hodin. S touto technikou jsem se setkal u systému Realspeak společnosti Nuance a je nutno uznat, že výstup tohoto softwaru se opravdu velmi, velmi podobal hlasu živého člověka. c) Účelová (domain-specific) syntéza – jsou nahrána celá slova, slovní spojení, či jednoduché věty. Použitelná je pouze v systémech, které mají omezenou množinu výstupních textů.
- 26 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Všechny principy, kromě posledně jmenovaného, nabízejí funkcionalitu, zvanou TextTo-Speech (TTS). To znamená, že zdrojovými daty takového syntezátoru jsou běžné texty. Takový text je TTS systémem nejprve v souladu s pravidly výslovnosti daného jazyka převeden z psané do fonetické podoby. Následně jsou jednotlivým entitám fonetického zápisu přiřazeny akustické reprezentace, podle příslušné techniky syntézy. Na závěr dokonalejší algoritmy TTS upraví intonaci hlasového výstupu v souladu s interpunkčními znaménky v textu, takže řeč nepůsobí monotónně. Systémů TTS existuje celá řada. Přímo v Asterisku existuje podpora pro Festival, svobodný TTS engine s difónovou syntézou. Anglický jazyk je ve Festivalu na celkem dobré úrovni, mohu-li to jako nerodilý mluvčí posoudit. Ovšem v případě českého jazyka se jedná o produkt v raném stádiu vývoje a obávám se, že po omezení šířky pásma pro telefonní kanál, by se veškeré stopy srozumitelnosti ztratily úplně. Nezbývá tedy, než se pro hlasový informační systém použít posledně zmíněnou metodu syntézy, tedy skládání předem nahraných slov, frází, či celých vět do výsledné zprávy. Ostatně všechny informační systémy v českém jazyce, se kterými jsem se měl možnost setkat, využívaly právě tento druh syntézy hlasu – namátkou například hlásiče zastávek prostředků MHD, staniční rozhlasy nádraží ČD (systém HIS od firmy Mikrovox, s.r.o. s mužským hlasem a systém IZE od firmy Elektročas, s.r.o. s ženským hlasem), služba přesného času 14112, hlasová schránka Memobox, hlasové pošty mobilních operátorů, atd. Jedinou výjimku tvořila dnes již neexistující služba voicegate.cz, s podtitulem „e-maily do ouška,“ která prostřednictvím Text-To-Speech předčítala volajícímu obsah jeho e-mailové schránky.
11.2. České číslovky v Asterisku Vzhledem k tomu, že Asterisk byl vyvinut v anglicky mluvící zemi, jsou prostředky hlasové syntézy navrženy pro angličtinu. Pouhá výměna zvukových vzorků za česky mluvící bohužel nepostačí, neboť jazyk český obsahuje takové záludnosti, jako skloňování (jedna koruna – jeden haléř) a nepřímočarou tvorbu množného čísla (dvě koruny – pět korun). Z tohoto důvodu nezbývá než celou inteligenci Asterisku, která dokáže vyslovovat číslovky, kompletně nahradit novou, která bude respektovat specifika českého jazyka.
- 27 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Pokud chceme vyslovovat číselné údaje, bylo by nejčistší nahrát každou číslovku zvlášť. To by však bylo velmi pracné, proto si problém můžeme zjednodušit tak, že nahrajeme pouze číslovky od 0 do 20 a dále jen celé desítky – 30, 40, 50, 60, 70, 80, 90. Při vyslovování číslel v rozsahu 20-99 vyslovíme nejprve počet desítek, následovaný počtem jednotek. Obdobně lze postupovat i se stovkami, tisíci, atd. Problémem však jsou mezery mezi slovy. Zatímco při vyslovení „dvacet jedna“ se mezi slovy neobjeví žádná tichá pasáž, dokonce bychom v záznamu i těžko hledali stopy po hlásce „t“,
Obrázek 11-1: oscilogram slova "dvacetjedna"
Obrázek 11-2: oscilogram slov "dvacet" "jedna" - 28 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
pokud vyslovíme zvlášť „dvacet“ a zvlášť „jedna“, nemůžeme se vložení mezery vyhnout. To ilustrují oscilogramy 11-1 a 11-2. Tento efekt však, kromě toho, že upozorňuje na strojový hlas, působí z hlediska srozumitelnosti spíše kladně, není důvod se mu vyvarovávat. Dále je zapotřebí vyřešit problém skloňování číslovek a přiřazení správného množného čísla. Jako příklad uveďme koruny a haléře. Správně česky vyslovujeme: „jedna koruna,“ „jeden haléř,“ „dvě koruny,“ „dva haléře,“ … „čtyři koruny,“ „čtyři haléře,“ a konečně „pět korun,“ „pět haléřů“. Bez ohledu na správnou mluvnickou kvalifikaci poukázaných jevů tedy můžeme konstatovat, že máme dva proměnné případy číslovek – 1 a 2. Dále máme celkem tři tvary „látek“: ●
„koruna“ a „haléř“ pro množství 1,
●
„koruny“ a „haléře“ pro množství 2-4,
●
„korun“ a „haléřů“ pro množství větší než 5
Dřívější pravidla spisovné češtiny navíc striktně vyžadovala proměnné tvary látek i u složených číslovek, zakončených na „jedna“ až „čtyři“, například tedy: „dvacet jeden haléř.“ Současná pravidla tento způsob stále připouštějí, nicméně od 70. let 20 století se prosazuje spíš podoba: „dvacet jedna haléřů,“ která je také z hlediska implementace jednodušší, protože výjimky z pravidelné výslovnosti se nám tak omezí pouze na množství 1-4.
11.3. Nahrávání hlasových nabídek Vzhledem k tomu, že pro připravované aplikace nebyl k dispozici úplný soubor hlásek v libovolném jazyce a jakékoli střídání mluvčích během hovoru působí velmi rušivě, musel jsem přistopit ke kroku nahrát si všechny potřebné hlásky sám. Rád bych zde prezentoval poznatky, ke kterým jsem došel. Co se týče technického zajištění nahrávek, ideální by samozřejmě bylo zvukotěsné nahrávací studio s kvalitním mikrofonem. Pro telefonní aplikace však bohatě dostačuje i běžný počítač s naprosto běžným mikrofonem, neboť jeho citlivost je v telefonním pásmu celkem dostatečná. Někdo dokonce doporučuje nahrávat hlasové nabídky přímo telefonním přístrojem, pomocí jednoduchého skriptu přímo v číslovacím plánu - 29 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Asterisku. To má výhodu v tom, že nahraný zvuk bude opravdu věrný telefonnímu hovoru, navíc není třeba řešit kódování do správných kodeků, atd. Bohužel, absence potřebného hardwaru pro připojení běžného telefonu k Asterisku, mi ani tuto možnost nedovolila vyzkoušet. K nahrávání jsem tedy použil běžný počítač. Jako nejvhodnější program se mi jevil svobodný zvukový editor Audacity (audacity.sf.net). Mikrofon jsem použil běžný elektretový a jako A/D převodník kodek standardu AC97, vestavěný do jižního můstku čipové sady intel 865PE. Jako vzorkovací frekvenci pro pořizování nahrávek jsem zvolil 48kHz a to z těchto důvodů: ●
Kodek AC97 hardwarově pracuje pouze v této vzorkovací frekvenci, všechny
ostatní vzorkovací frekvence jsou softwarově interpolovány uvnitř ovladače zvukového čipu. ●
Tento kmitočet je celistvým šestinásobkem standardní telefonní vzorkovací
frekvence 8 kHz, takže následná decimace na tento kmitočet je téměř bezeztrátová (s přihlédnutím k šestinové šířce pásma) ●
Nahrávky je výhodné až do samého konce produkčního řetězce držet v co
nejvyšší kvalitě. Pak nebude například problém přejít na kvalitnější telefonní kodek G.722, který používá vzorkovací kmitočet 16 kHz a přenáší tak hovorový signál s horním mezním kmitočtem cca. 7 kHz
Obrázek 11-3: oscilogram nahrávky - příklad - 30 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Při nahrávání je důležité zachovat co největší odstup signálu od šumu. Vzhledem k tomu, že použitý zvukový hardware nebyl primárně stavěn pro vysoce kvalitní zvukové nahrávky, zvukové cesty jsou téměř neoddělené od ostatních digitálních signálů na základní desce počítače, úroveň šumu dosahuje v mém případě až -40 dB, jeli 0 dB maximální vybuzení vstupu. Tento šum je pravděpodobně způsoben především předzesilovačem, neboť jeho úroveň se tlumí se ztlumováním vstupu. Experimentálně jsem ověřil, že aby byl šum ve výsledné nahrávce téměř neznatelný, je třeba vstup vybudit až k hodnotě 0 dB. Zároveň je nutné zajistit, aby nedošlo k přebuzení vstupu a tvrdé limitaci signálu. Nakonec jsem dospěl k částečně kompromisnímu řešení, kdy se průměrná hodnota vybuzení pohybuje kolem -3 dB a ačkoli se vybuzení občas dotkne hranice 0 dB, neprojeví se limitace na poslech rušivě. Oscilogram takového záznamu je na obrázku 11-3 na straně 30. Po úspěšném nahrání je možné na záznam ještě aplikovat filtry, například odstranit stejnosměrnou složku a vyrovnat špičkovou hlasitost na 0 dB. Je možné také si pohrát s různými kmitočtovými filtry pro zlepšení subjektivní kvality záznamu. Nakonec je potřeba nastříhat záznam na jednotlivé části s přihlédnutím k dodržování konstatních mezer před a za záznamem. Jednotlivé nastříhané části jsem poukládal jako wav soubory. Důležité je, aby po rozstříhání na jednotlivé části byly následující úpravy prováděny konzistentně na všech souborech. Zároveň je třeba se vyvarovat takovým úpravám, jako třeba normalizace hlasitosti každého záznamu zvlášť, to by způsobilo „skákání“ hlasitosti celé zprávy.
- 31 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Pro dávkovou úpravu a konverzi všech souborů se programy s grafickým rozhraním, jako např. Audacity, nejeví jako příliš vhodné. K tomuto účelu jsem proto použil program SoX. Jedná se o zkratku „Sound eXchange“ a jde o univerzální multiplatformní konvertor audioformátů s možností filtrování signálu. Od něj jsem požadoval především decimaci vzorkovacího kmitočtu na 8 kHz a konverzi do nějakého standardního telefonního kodeku – v mém případě jsem vyzkoušel G.711 podle A-zákona a GSM kodek. Tato konverze není nezbytná, neboť Asterisk automaticky, v případě neshody použitelných kodeků na obou stranách hovoru, transkóduje v reálném čase hovorová data. Je ale zbytečné takto plýtvat prostředky ústředny, lepší je připravit si nahrávky do požadovaných kodeků předem. Po praktickém testování jsem své požadavky na program SoX rozšířil o požadavek filtrování signálu horní propustí s mezní frekvencí 300 Hz, neboť ač záznam se vzorkovací frekvencí 48 kHz zní čistě a věrohodně, po filtrování a decimaci na vzorkovací kmitočet 8 kHz
0
se ztráta vysokofrekvenčních
pùvodní záznam po decimaci po decimaci a filraci
-10
-20
dB
-30
-40
-50
-60
-70 10
100
1000 Hz
Obrázek 11-4: ukázka spektra řečového signálu - 32 -
10000
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
složek projeví jako přebytek nízkých frekvencí a srozumitelnost je tím omezena. Zařazením horní propusti je toto kompenzováno, navíc je tak lépe splněno pásmo 300 – 3400 Hz pro telefonní kanál. Ukázka spektra řečového signálu je na obrázku 114. Jak již jsem uvedl, program SoX je ovládán z příkazového řádku. Nepodporuje dávkové zpracování, při více vstupních souborech se všechny soubory spojí do jednoho výstupního. Proto jsem pro dávkové zpracování všech souborů napsal jednořádkový skriptík: #!/bin/bash sox -S "$1" -r 8000 -t al "${2}/${1%%wav}alaw" highpass 300 Tento skript převezme jako první parametr jméno souboru a jako druhý parametr cílovou cestu, kam ukládat transkódované soubory. Volba -S zajistí vypisování informací o běhu, volba -r 8000 zajistí konverzi vzorkovací frekvence, -t al nastaví formát výstupního souboru na G.711 A-zákon a nakonec highpass 300 zařadí horní propust s mezní frekvencí 300 Hz. Tento skript už je možné volat příkazem find, a tak zajistit dávkové zpracování všech souborů: # find \*.wav -exec soxconv.sh {} ../alaw \; Tento příkaz zavolá předchozí skript a předá mu postupně název každého souboru, následovaný cestou k cílovému adresáři. Drobnou úpravou skriptu můžeme zakódovat zvuky také do GSM kodeku. Takto zpracované soubory již je možné vystavit někam, kde je Asterisk najde. Standardní cesta je /var/lib/asterisk/sounds.
11.4. Požadavky na mluvčího Technickou stránku problému pořizování vlastních nahrávek jsem již probral dostatečně. Zbývá zmínit požadavky na mluvčího.
- 33 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Především je dobré, mít před započetím nahrávání přesně rozmyšleno, jaké fráze je třeba nahrát a jak má vypadat finální sestava. Není dobré spoléhat v tomto na vlastní pamět, i při nahrávání číslovek se vyplatí, mít je napsané na papíře a to v rozepsaném stavu (tím se předejde pozdějším problémům typu – mám tu jedničku přečíst jako „jedna,“ „jeden,“ nebo „jednička?“). Příklad takového textu je na obrázku 11-5.
tón oznámí
jednu hodinu, dvě minuty, přesně.
tón oznámí
tři hodiny, čtyři minuty, deset sekund pět hodin, šest minut, dvacet sekund sedm hodin, osm minut, třicet sekund devět hodin, jedenáct minut, čtyřicet sekund dvanáct hodin, třináct minut, padesát sekund čtrnáct hodin, patnáct minut, přesně. šestnáct hodin, sedmnáct minut, deset sekund. osmnáct hodin, devatenáct minut, třicet sekund dvacet jedna hodin, dvacet dva minut, přesně dvacet tři hodin, nula minut, padesát sekund
nula hodin přesně Obrázek 11-5: Příklad textu pro mluvčího
Dále se mi velice osvědčilo nahrávání frází ve větách, které odpovídají budoucímu výstupu informačního systému. I když jde o zdlouhavější techniku, výsledné záznamy za to stojí, speciálně u číslovek. Pokud totiž dostane člověk za úkol vyslovit číslovky „jedna“ až „deset“ jen tak za sebou, má automaticky sklony k tomu, použít intonaci jako pro jednu dlouhou větu, což je špatně. Pokud je informací opravdu hodně, a nahrávání v souvislých větách by bylo až příliš zdlouhavé, je možné přejít i k seznamu, ale i v tom případě doporučuji před a za nahrávané slovo vložit nějaká krátká „ochranná slova,“ která zabezpečí bezchybnou intonaci.
- 34 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Před vlastním nahráváním je dobré se rozmluvit. Obvykle k tomu stačí, přečíst si jednou nanečisto „scénář.“ Pokud je potřeba ke stávajícímu záznamu přidat nové fráze, je nanejvýš vhodné, aby si mluvčí poslechl svůj minulý záznam a naladil svou intonaci do shody s tou minulou, neboť výška i barva lidského hlasu se v čase poměrně výrazně mění. Pro vlastní nahrávání je nutné mluvčího požádat, aby vyslovoval „jako robot“, aby prodlužoval mezery mezi slovy a to zejména u složených číslovek, jako „dvacet jedna“ (viz obr. 11-1 a 11-2 na straně 28). Také je třeba dát pozor na hlasitost projevu – nemělo by se jednat o šepot ani křik, a na vzdálenost a umístění mikrofonu – při velké vzdálenosti se více projeví akustika místnosti, ve které nahráváme, což není žádoucí; při malé vzdálenosti se naopak může začít zkreslovat frekvenční charakteristika mikrofonu. Je nutné vyvarovat se přímému směrování mikrofonní vložky na ústa, tím by došlo k výraznému zkreslení při vyslovování takzvaných explozív, tj. hlásek typu „p,“ „b,“ „t,“ kdy z úst vystřelují proudy vzduchu.
12. Řešení aplikace „přesný čas“ 12.1. Úvod Nyní se již dostáváme k praktické realizaci hlasových informačních systémů s použitím rozhraní Asterisk Gateway Interface. Jako první bude diskutováno řešení aplikace, která volajícímu sdělí informaci o přesném čase. Jedná se tedy vlastně o klon informační služby, která leta fungovala ve veřejné telefonní síti na čísle 112, po přečíslování funguje dodnes na čísle 14112, neboť její původní číslo převzalo jednotné evropské číslo tísňového volání. Možná by se mohlo zdát zbytečné, pokoušet se vyrábět službu, která je ve veřejné síti k dispozici. Jedním z důvodů pro, může být například cena veřejné služby, která se pohybuje kolem 3 Kč za minutu. Nasazení takové aplikace na pobočkové ústředně tak můžeme uspořit nemalé finanční prostředky, jsou-li účastníci navyklí tuto službu využívat. Jedná se o neinteraktivní službu, jejíž jediným vstupem je přesný čas. Jako zdroj přesného času můžeme použít hodiny operačního systému, pokud zajistíme jejich synchronizaci podle nějakého externího časového normálu. V případě počítače,
- 35 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
připojeného do Internetu, můžeme s výhodou využít synchronizaci času z časových serverů v Internetu prostřednictvím protokolu NTP (RFC 1305). Při použití NTP démona má počítač trvale navázáno spojení hned s několika NTP servery a případné odchylky hodin operačního systému dorovnává zrychlením či zpomalením jejich chodu, takže nedochází ke skokovým posunům času.
12.2. Popis realizace Vývojový diagram programu je na obrázku 12-1 na straně 37. Nejprve je třeba linku vyzvednout. Jak jsem během vývoje prakticky ověřil, aplikace bez vyzvednutí linky funguje na první pohled naprosto stejně, volající totiž nepozná zda hlásky dostává místo vyzváněcího tónu, nebo až po vyzvednutí linky. Pokud ale linka vyzvednutá není do určitého časového limitu, volající strana obvykle spojení zruší. Po vyzvednutí linky je třeba nalézt v blízké budoucnosti časový okamžik, vhodný k vyhlášení. Nejkratší mezidobí dvou časových okamžiků, které je možné běžnou rychlostí řeči vyhlásit, je 10 sekund. Takže je potřeba najít nejbližsí celou desítku sekund, která je zároveň vzdálena nejméně 7 sekund, aby byl čas na vyhlášení. Toto je také drobný rozdíl oproti veřejné službě 14112, která běží trvale a po vytočení je k ní účastník připojen v libovolném okamžiku. Následuje tedy vyhlášení času ve tvaru například: „Tón oznámí“ „dvacet jedna“ „hodin“ „dvacet“ „dva“ „minut“ „padesát“ „sekund.“ (Uvozovky ohraňičují jednotlivé záznamy). Vzhledem k tomu, že hodin je maximálně 23, rozhodl jsem se výjimečně nahrát i složené číslovky 21, 22 a 23 a vyhnout se tak umělému skládání. V případě minut jsem si to už nemohl dovolit, a aby byla zachována konzistence, dochází k umělému skládání i u zmíněných číslovek. V případě sekund se předpokládá pouze vyhlašování celých desítek, skládání číslovek tedy nepředstavuje problém. Pokud čas končí na samé nuly, je na místo nul vysloveno „přesně“. Slovo „přesně“ také nahrazuje poněkud neohrabané „nula“ „sekund“. Po vyhlášení času nezbývá, než počkat na dosažení vyhlášeného času. Nejprve je celý proces uspán příkazem sleep, aby se probudil nejdříve sekundu před a nejpozději těsně před vyhlášeným časem. Následuje smyčka, ve které se trvale porovnává reálný čas s vyhlášeným, aby byl nalezen přesný okamžik překlopení času. Z hlediska - 36 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Vyzvednutí linky
Nalezení nejbližšího času, vhodného k vyhlášení
Vyhlášení času
Sleep do (vyhlášený - 1)
Načtení aktuálního času
aktuální >= vyhlášený ?
ne
ano
Přehrání tónu Obrázek 12-1: vývojový diagram aplikace "přesný čas" - 37 -
Mikrospánek
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
přesnosti by se mohlo zdát ideální nechat smyčku prázdnou, aby testování reálného času probíhalo kontinuálně. Vzhledem ke způsobu, jakým operační systém Linux přiděluje procesor jednotlivým procesům, by takové řešení bylo krajně nevhodné. Operační systémy UNIXového typu se totiž standardně chovají spravedlivě k procesům. Každému procesu je totiž přidělen procesor pouze na určitou dobu, standardně 100 us. Po uplynutí této doby je procesor přidělen dalšímu procesu a procesu, kterému byl procesor
odebrán,
je
přiřazena
nejnižší
priorita,
takže
se dostane
na řadu,
až se všechny ostatní vystřídají. Tento scénář ale neplatí, pokud se proces přiděleného procesoru vzdá dobrovolně, právě prostřednictvím funkce sleep, resp. usleep. V takovém případě je procesu, po uplynutí doby spánku, přidělena vysoká priorita, a dostane se tedy k procesoru dříve. Takže zatímco prázdná smyčka by udělala několik tisíc porovnání časů během prvních 100 us, načež by byl proces zablokován, vložíme-li do smyčky „mikrospánek“ například 50 ms, dostaneme velice pravidelnou sérii jednoho testu každých cca. 50 ms. Tato změna se také příznivě projeví na celkové zátěži systému v poslední sekundě před vyhlášením času. Pro vlastní realizaci jsem zvolil programovací jazyk C. Úplný zdrojový text programu je přílohou tohoto textu. Aby byly splněny požadavky pro správnou komunikaci s rozhraním AGI, je příkazem setlinebuf(stdout) zakázáno použití vyrovnávací paměti pro víc než jeden řádek textu. Vzhledem k tomu, že aplikace nepožaduje žádné informace od Asterisku, jsou na začátku všechny proměnné pouze načteny a zahozeny. To samé se děje i s odpověďmi Asterisku na zaslané příkazy, neboť výsledek příkazů není pro další chod programu nijak rozhodující.
12.3. Kompilace a testování Program je celý obsažen v jednom souboru, navíc používá pouze standardní knihovnu jazyka C, takže je možné ho přeložit příkazem: # gcc -o presnycas.agi presnycas.c Pokud přidáme ještě parametr -DDEBUG, bude program na standardní chybový výstup, a tedy i na konzoli Asterisku, vypisovat, v jaké fázi se právě nachází. Pro testování je možné program jednoduše spustit z příkazového řádku. Program nejprve načítá kanálové proměnné, je tedy třeba poslat mu prázdný řádek. Poté je - 38 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
potřeba na každý příkaz odpovědět alespoň prázdným řádkem. Toto můžeme nasimulovat také takovouto sekvencí příkazů: # (echo '' ; yes) | ./presnycas.agi Pro instalaci do Asterisku je potřeba spustitelný soubor nakopírovat nebo nalinkovat do adresáře, kde jsou AGI skripty uloženy, standardně /var/lib/asterisk/agibin/. Také je potřeba umístit zvukové soubory do patřičné cesty, v mém případě /var/lib/asterisk/sounds/presnycas/.
- 39 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
13. Řešení aplikace „kurzovní lístek“ 13.1. Úvod Jako druhá druhá v pořadí bude prezentována aplikace pro automatizované informování volajícího o kurzovním
Vyzvednutí linky, uvítání
lístku devizového trhu, který vydává Česká národní banka. Tentokrát se bude jednat o interaktivní službu, kde volající stisknutím tlačítek volí, o jaké informace
Je lokální soubor validní?
ano
má zájem. O něco komplikovanější také bude získávání informací – ty je třeba stáhnout z webových stránek České národní banky. Stažený kur-
ne
zovní lístek je také potřeba ukládat lokálně, neboť při velkém zájmu
Stažení dat z webu
o službu by docházelo k několikanásobnému zbytečnému stahování téhož, což by přinejmenším odporovalo netiketě.
Zpracování souboru
13.2. Popis realizace Vývojový diagram aplikace je na obrázku 13-1. Nejprve je linka vyzvednuta a volající přivítán. Násle-
Hlavní menu
duje test, zda je lokální datový soubor validní, tedy zda existuje, má nějakou nenulovou velikost a jeho datum modifikace není starší než
Přehrání zvolené nabídky
14 hodin, 35 minut včerejšího, resp. dnešního dne (ve 14:30 zveřejňuje
Obrázek 13-1: vývojový diagram aplikace "kurzovní lístek" - 40 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
ČNB aktuální kurzovní lístky). Pokud lokální soubor testu vyhoví, je použit. V opačném případě je stažen aktuální kurzovní lístek z webu ČNB a lokální soubor je přepsán staženým. Dalším krokem je zpracování souboru. Textový soubor je načten a jednotlivé informace v něm obsažené jsou uloženy do odpovídající datové struktury tak, aby byly dalším částem programu snadno dostupné. Poté je volajícímu přehráno menu, kde se pod jednotlivými čísly skrývají různé měny. Tato nabídka je přehrávána tak dlouho, dokud nějaké z nabízených tlačítek není stisknuto. Po stisknutí tlačítka je příslušná měna vyhledána v datové struktuře a přehrána volajícímu, načež se program vrátí zpět do hlavního menu. K praktické realizaci jsem tentokrát zvolil jazyk PHP, zejména kvůli jednoduché práci s řetězci. Navíc jsem se rozhodl komunikaci s Asteriskem svěřit knihovně phpagi, která se sama postará o dodržování komunikačního protokolu. Úplný výpis programu je opět přílohou tohoto textu, proto zde pouze uvedu významné části programu. Program obsahuje celkem pět funkcí, které představím předem. Funkce parseListek(array
&$text,
array
&$listek), přečte textový
soubor s kurzovním lístkem (obr. 13-2), který je obsažen v poli $text (každý řádek je jedna položka v poli – výstup funkce file() ) a vytvoří datovou strukturu $listek typu dvojrozměrné pole, kde prvním indexem je druh informace (země, měna, množství, kód, kurz) a druhým indexem je číslo řádku textového souboru, na kterém se daná informace vyskytuje. Navíc funkce zajistí převod velikosti kurzu z řetězce na desetinné číslo (v původním textovém souboru se jako oddělovač desetinných míst používá čárka, takže by k automatickému převodu nedošlo). 09.08.2007 #153 země|měna|množství|kód|kurz Austrálie|dolar|1|AUD|17,506 Bulharsko|lev|1|BGN|14,373 Čína|renminbi|1|CNY|2,707 Dánsko|koruna|1|DKK|3,776 EMU|euro|1|EUR|28,110 Estonsko|koruna|1|EEK|1,797 Filipíny|peso|100|PHP|45,200 Hongkong|dolar|1|HKD|2,617 Chorvatsko|kuna|1|HRK|3,844
Obrázek 13-2: formát textového souboru kurzovního lístku ČNB - 41 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Další funkce realizuje hlasové menu s nabídkou kurzů jednotlivých měn. Inspiroval jsem se zde obdobnou funkcí v knihovně phpagi, kterou jsem upravil pro konkrétní účel. Funkce přebírá jako první parametr pole dvojic klíč - hodnota, kde klíč označuje tlačítko, které je třeba stisknout a hodnota obashuje kód měny, který k danému tlačítku patří. Dalšími parametry funkce je booleovská proměnná $hlavni, která, je-li pravdivá, je menu označeno hlasovou návěstí „hlavní nabídka,“ a číselná hodnota $timeout, která udává dobu před opakováním celé nabídky. Zvláštní funkci má tlačítko 0, které neudává žádnou měnu, ale informuje o platnosti kurzovního lístku. Proto je funkce tlačítka nula naprogramována napevno. Následující funkce prectiCislo(int $cislo), je pouze servisní pro ostatní funkce. Přečte správně číslo v rozsahu 0-199, ale neřeší speciální případy pro čísla 1-4, díky tomu je univerzální, jak pro použití s korunami, tak s haléři a dokonce i pro poslední dvojčíslí letopočtu. Číslo 1 čte jako „jedna“, číslo 2 jako „dva“. Funkce vyhlasKurz(&$listek, &$kodMeny, $aktualni = TRUE) je určena k vyhlášení hodnoty kurzu dané měny. Jako argumety přijímá jednak datovou strukturu s kurzovním lískem, pak také kód měny, jejíž kurz má být vyhlášen. Pokud se daný kód měny v datové struktuře nenáchází, funkce vyhlásí „Došlo k chybě“. V opačném případě je vyhlášeno například „Aktuální kurz“ „eura“ „je“ „dvacet“ „osm“ „korun,“ „pět“ „haléřů“. V případě, že je kurz určen za jiné množství než 1 měnovou jednotku, je na konec zprávy přidána ještě hláska například: „za“ „sto“ „slovenských korun.“ Původně jsem měl v plánu přeformulovat větu tak, aby se dal využít dvakrát stejný záznam pro danou měnu, ale ukázalo se to jako těžko proveditelné. Proto je u takových měn nutno nahrát kromě základního záznamu ve 2. pádu jednotného čísla také záznam v 2. pádu množného čísla. Poslední funkce prectiDatum(int $timestamp), má za úkol přečíst datum, které je dané UNIXovým časovým razítkem. Složitost algoritmu není veliká, bohužel pro vyslovení data nemůžeme použít žádné dosud nahrané zvuky, neboť potřebujeme řadové číslovky pro dny a navíc názvy všech měsíců (označovat měsíce číslovkami nemá cenu – stejně je nemáme v požadovaném tvaru nahrány). Pro letopočet jsem použil následucí trik – nahrál jsem fráze „dva tisíce“ a „tísíc devět set“ a zbytek letopočtu předám funkci prectiCislo(). - 42 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Nyní se již dostávám k popisu hlavního programu. Nejprve nastaveno omezení maximální doby běhu programu 360 sekund, tedy 10 minut. Dále je vypnuto hlášení chyb příkazem error_reporting(0). Tento příkaz je vhodné při odlaďování zakomentovat. Následuje načtení knihovny phpagi a vytvoření instance třídy AGI(). Tím také dojde k načtení kanálových proměnných z Asterisku. Pak už je pomocí metod objektu $agi vyzvednuta linka a přehrán uvítací text. Knihovna phpagi bohužel nenabízí tzv. neblokující verze příkazů, to znamená, že zavolání každého příkazu zablokuje běh programu až do doby, než je příkaz vykonán a Asterisk odpoví. To je ale nevhodné pro následující část programu, kdy se testuje validita lokálního souboru. Ty testy, a případné stahování aktuální verze, totiž trvají určitou dobu a po tu dobu by na lince bylo ticho. Proto v tomto kroku obcházím knihovnu phpagi a posílám asterisku přímo příkaz, aby přehrál hlásku „čekejte prosím“ a během této hlásky dané testy proběhnou. V případě, že se některý krok zdrží, zůstane linka potichu, ale volající na to bude předešlou hláskou upozorněn. Obvykle se ale vše stihne do té doby, než hláska skončí. Na konci celého zpracování textového souboru je načtena (a zahozena) odpověď asterisku tak, aby další příkazy mohli bez problému využívat knihovní funkce. Program končí nekonečnou smyčkou, ve které je načtena volba volajícího z funkce menu() a následně buď vyhlášen kurz zvolené měny, nebo vyhlášeno datum platnosti kurzovního lístku.
13.3. Testování a instalace PHP je skriptovací jazyk, to znamená že skripty nejsou kompilovány, ale interpretovány. K jejich provozu je ale potřeba mít nainstalovaný interpreter jazyka PHP, a to ve verzi pro příkazový řádek. Program byl testován na verzi PHP 5.2.3. Pro testování můžeme opět použít přímé spuštění skriptu z příkazového řádku. Tentokrát ale na každý příkaz musíme odpovědět alespoň číslem 200, prázdné řádky knihovna phpagi ignoruje. Pokud chceme předat informaci o stisknutém tlačítku, musíme za číslo 200 ještě připsat mezeru a ASCII kód stisknutého tlačítka v dekadické soustavě zapsat za výraz result=. - 43 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
Instalace opět spočívá v nakopírování nebo nalinkování skriptu do příslušné cesty (v případě
kopírování
je
třeba
kopírovat
i soubory
phpagi.php
a phpagi-
astmanager.php) a obdobné nahrání všech zvukových souborů. Kromě toho je potřeba v konfiguračním souboru interpreteru jazyka PHP, v souboru php.ini, nastavit volbu allow_url_fopen na On. Tím je povoleno v příkazech, které slouží původně pro práci se soubory, pracovat také s webovými cestami (URL) a toho program využívá. Na začátku zdrojového textu souboru ještě můžeme upravit přesnou URL souboru s aktuálním kurzovním lístkem, název lokálního souboru (bude vytvořen v dočasném adresáři daném konfiguračním souborem knihovny phpagi) a především pole, které určuje pod kterým tlačítkem bude která měna.
14. Závěr V rámci bakalářského projektu jsem se seznámil s vlastnostmi softwarové ústředny Asterisk. Prošel jsem základní možnosti konfigurace ústředny a kladl přitom důraz na protokol SIP, neboť patří v současnosti bezesporu k nejrozšířenějším protokolům pro přenos hlasu v IP sítích. Také jsem prozkoumal možnosti databáze AstDB a rozhraní Asterisk Gateway Interface. Tyto znalosti jsem pak v bakalářské práci použil pro realizaci dvou hlasových informačních služeb - „přesný čas“ a interaktivní „kurzovní lístek“. Obě
služby
jsem
nakonec
dokonce
nainstaloval
na svůj
osobní
počítač
a prostřednictvím protokolu SIP připojil k síti VoIP operátora 802.cz, jehož hlasové služby již delší dobu využívám. Díky tomu bylo možné obě služby otestovat přímo ve veřejné telefonní síti a na základě zkušeností z takového provozu aplikace doladit pro maximální použitelnost.
- 44 -
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE, FAKULTA ELEKTROTECHNICKÁ
Asterisk Gateway Interface
15. Použitá literatura [1]
Meggelen, Jim Van. Asterisk : the future of telephony / Beijing : O'Reilly, c2005. xxii, 380
s. : ISBN 0-596-00962-3 [2]
Asterisk Guru [online]. [2007] [cit. 2007-01-09]. Dostupný z WWW:
. [3]
Arte Marketing. Voip-info.org : Wiki [online]. c2003-2006 [cit. 2007-01-09]. Dostupný
z WWW:
. [4]
Digium. Asterisk [online]. c2007 [cit. 2007-01-09]. Dostupný z WWW:
. [5]
Threat Models, and Security Services for Videoconferencing over Internet2 [online].
Version 0.1. 2002 [cit. 2007-01-09]. Dostupný z WWW: . [6]
Wikipedia contributors. Speech synthesis [online]. Wikipedia, The Free Encyclopedia.,
c2007 , last revision: 2 August 2007 13:51 UTC [cit. 2007-08-05]. Dostupný z WWW: . [7]
ČMEJLA, Roman. FORMANTOVÁ ANALÝZA A SYNTÉZA [online]. c2007 [cit. 2007-08-05].
Dostupný z WWW: . [8]
ÚJČ AV CŘ. Jazyková poradna ÚJČ AV ČR: FAQ : Na co se nás často ptáte [online]. [2007]
[cit. 2007-08-05]. Dostupný z WWW: . [9]
Stones, Richard. Linux : začínáme programovat / Praha : Computer Press, c2000. xxxviii,
895 s., příl. : ISBN 80-7226-307-2.
16. Seznam příloh 1. Zdrojový kód aplikace „přesný čas“ 2. Zdrojový kód aplikace „kurzovní lístek“
- 45 -
Příloha 1: zdrojový kód aplikace „přesný čas“ #include #include #include #include
<stdio.h> <string.h>
void streamFile(char* filename) { char buf[80]; fprintf(stdout,"STREAM FILE \"presnycas/%s\" \"\" \n", filename); fgets(buf, 80, stdin); } void sayNum(int number) { char buf[80]; fprintf(stdout,"STREAM FILE \"presnycas/%i\" \"\" \n", number); fgets(buf, 80, stdin); } int main() { time_t aktualni, vyhlaseny, zbytek; char buf[80]; struct tm *cas; setlinebuf(stdout); setlinebuf(stderr); /* nacteni kanalovych promennych - zahazuje se */ do fgets(buf, 80, stdin); while (strlen(buf)>1); fprintf(stdout,"ANSWER \n"); fgets(buf, 80, stdin); for (;;) { time(&aktualni); /*pocet sekund do nejblizsi desitky*/ zbytek = 10 - (aktualni % 10); /*zbyva-li mene nez 7 sekund, vyhlas dalsi desitku*/ vyhlaseny = zbytek>7? aktualni + zbytek: aktualni + zbytek + 10; #ifdef DEBUG fprintf(stderr,"Ton oznami: %s", ctime(&vyhlaseny)); #endif cas= localtime(&vyhlaseny); streamFile("tonoznami"); if (cas->tm_hour == 1) { streamFile("jednu"); streamFile("hodinu"); } else if (cas->tm_hour == 2) { streamFile("dve"); streamFile("hodiny"); } else if (cas->tm_hour > 2 && cas->tm_hour < 5) { sayNum(cas->tm_hour);
-i-
streamFile("hodiny"); } else if (cas->tm_hour < 24) { sayNum(cas->tm_hour); streamFile("hodin"); } if (cas->tm_min == 0 && cas->tm_sec == 0) streamFile("presne"); else { if (cas->tm_min == 1) { streamFile("jednu"); streamFile("minutu"); } else if (cas->tm_min == 2) { streamFile("dve"); streamFile("minuty"); } else if (cas->tm_min > 2 && cas->tm_min < 5) { sayNum(cas->tm_min); streamFile("minuty"); } else if (cas->tm_min < 20) { sayNum(cas->tm_min); streamFile("minut"); } else if (cas->tm_min < 60) { //slozeni ze dvou cislovek - desitky, jednotky sayNum((cas->tm_min / 10) * 10); if (cas->tm_min % 10) sayNum(cas->tm_min % 10); streamFile("minut"); }
}
if (cas->tm_sec == 0) streamFile("presne"); else if (cas->tm_sec < 60) { //zde by mely byt pouze cele desitky sekund sayNum((cas->tm_sec / 10) * 10); if (cas->tm_sec % 10) sayNum(cas->tm_sec % 10); streamFile("sekund"); }
zbytek = vyhlaseny - time(0) - 1; #ifdef DEBUG fprintf(stderr,"Sleeping : %li \n", zbytek); #endif if (zbytek>0) sleep(zbytek); while (vyhlaseny > time(0) ) usleep(50000); #ifdef DEBUG fprintf(stderr,"beep!\n\n"); #endif fprintf(stdout,"STREAM FILE beep \"\" \n"); fgets(buf, 80, stdin); } exit(0); }
- ii -
Příloha 2: zdrojový kód aplikace „kurzovní lístek“ #!/usr/bin/php -q 'EUR', '2'=>'USD', '3'=>'GBP', '4'=>'SKK', '5'=>'AUD'); $soundPrefix="listek/"; // FUNKCE /** * Nacte pole, obsahujici textovy listek CNB a * parsuje ho do odpovidajici datove struktury. * * @param array &$listekFile textovy soubor, vystup funkce file() * @param array &$listek pole, obsahujici rozdelene infromace ze souboru. */ function parseListek(&$kurzovniListek, &$listek){ $listek = array(); list($listek['datum'], $listek['poradi'])=explode(" ",$kurzovniListek[0]); for( $i=2; $i < sizeof($kurzovniListek) ; $i++ ) { list($listek['zeme'][$i], $listek['mena'][$i], $listek['mnozstvi'][$i], $listek['kod'][$i], $listek['kurz'][$i]) = explode('|',$kurzovniListek[$i]); // prevod kurzu na float cislo $listek['kurz'][$i]=0+str_replace(',','.',$listek['kurz'][$i]); } } //--------------/** * Precte hlavni menu podle pole $volby, napr.: * $volby = array( '1' => 'EUR', '2' => 'USD' ) * Precte 'Pro kurz eura, stisknete jednicku, * pro kurz am. dolaru stisknete dvojku... * * @param array &$volby pole prirazujici kody men k tlacitkum, * ktere se maji stisknout * @param bool $hlavni Zacne hlaskou 'Hlavni menu', default true * @param int $timeout Doba, po ktere se Menu zacne opakovat, default 5s. * @returns string, ASCII kod stisknuteho tlacitka */ function menu(&$volby, $hlavni=true, $timeout=5000) { global $agi, $soundPrefix; $keys = implode('', array_keys($volby)).'0'; $volba=NULL; if ($hlavni) { $ret = $agi->stream_file($soundPrefix.'hlavni_menu',$keys);
- iii -
}
if($ret['result'] != 0) $volba = chr($ret['result']);
while(is_null($volba)){ foreach($volby as $cislo => $kodmeny){ $ret = $agi->stream_file($soundPrefix.'pro_kurz_long',$keys); if($ret['result'] > 0) { $volba = chr($ret['result']); break; } $ret = $agi->stream_file($soundPrefix.'meny/'.$kodmeny,$keys); if($ret['result'] > 0) { $volba = chr($ret['result']); break; } $ret = $agi->stream_file($soundPrefix.'tlacitka/stisknete',$keys); if($ret['result'] > 0) { $volba = chr($ret['result']); break; } $ret = $agi->stream_file($soundPrefix.'tlacitka/'.$cislo,$keys); if($ret['result'] > 0) { $volba = chr($ret['result']); break; } } if(is_null($volba)){ $ret = $agi->stream_file($soundPrefix.'pro_info_0',$keys); if($ret['result'] > 0) return chr($ret['result']); $ret = $agi->wait_for_digit($timeout); if($ret['result'] > 0) { $volba = chr($ret['result']); if (!array_key_exists($volba, $volby)) $volba=NULL; } } } return $volba;
} //-------------------------/** * Precte cislo 0-199. Neresi sklonovani pro specialni pripady (1-4) * * @param int $cislo cislo, ktere ma byt precteno * @return bool FALSE, pokud cislo lezi mimo rozsah */ function prectiCislo($cislo){ global $agi, $soundPrefix; if ($cislo < 0 || $cislo > 199) return FALSE; if ($cislo >= 100) { $agi->stream_file($soundPrefix.'cislovky/100'); if (! $cislo %= 100) return TRUE; } // Ted uz je cislo jen mezi 0-99 if ($cislo >= 20) {
- iv -
$desitky = (int) ($cislo - ($cislo % 10)); $agi->stream_file($soundPrefix.'cislovky/'.$desitky); if (! $cislo %= 10) return TRUE; } // Ted uz je cislo jen mezi 0-19 $agi->stream_file($soundPrefix.'cislovky/'.$cislo); return TRUE; } //--------------------/** * Vyhlasi kurz meny $kodMeny, z kurzovniho listku $listek * * @param array &$listek Kurzovni listek, vystup funkce parseListek() * @param string &$kodMeny Tripismenny kod meny, tak jak je uveden v $listek * @param bool $aktualni Indikuje aktualni kurz. Default true. */ function vyhlasKurz(&$listek, &$kodMeny, $aktualni=true){ global $agi, $soundPrefix; $index = array_search($kodMeny, $listek['kod']); if ($index === FALSE) { //Pokud nebyl kod meny nalezen $agi->stream_file($soundPrefix.'doslo_k_chybe'); return; } if ($aktualni) $agi->stream_file($soundPrefix.'aktualni_kurz'); else $agi->stream_file($soundPrefix.'kurz'); $agi->stream_file($soundPrefix.'meny/'.$kodMeny); $agi->stream_file($soundPrefix.'je'); // nasleduje rozklad cisla na foneticke entity // ve formatu "dvacet", "osm", "korun", "trinact", "haleru" $korun = (int) $listek['kurz'][$index]; $haleru = (int) round(($listek['kurz'][$index] - $korun)*100); // Zvlastni pripady: // 1 koruna, 2-4 koruny 5- korun if ($korun == 1) { $agi->stream_file($soundPrefix.'cislovky/1'); $agi->stream_file($soundPrefix.'koruna'); } elseif ($korun == 2) { $agi->stream_file($soundPrefix.'cislovky/dve'); $agi->stream_file($soundPrefix.'koruny'); } elseif ($korun < 5 && $korun > 2) { $agi->stream_file($soundPrefix.'cislovky/'.$korun); $agi->stream_file($soundPrefix.'koruny'); } else { prectiCislo($korun); $agi->stream_file($soundPrefix.'korun'); } // Zvlastni pripady: // 1 haler, 2-4 halere, 5- haleru if ($haleru == 1) { $agi->stream_file($soundPrefix.'cislovky/jeden'); $agi->stream_file($soundPrefix.'haler'); } elseif ($haleru < 5 && $haleru > 1) { $agi->stream_file($soundPrefix.'cislovky/'.$haleru); $agi->stream_file($soundPrefix.'halere');
-v-
} else { prectiCislo($haleru); $agi->stream_file($soundPrefix.'haleru'); } // V pripade, ze kurz neni za 1 MJ, je treba dodat: // "za" "sto" "slovenskych korun." if ($listek['mnozstvi'][$index] == 100) { $agi->stream_file($soundPrefix.'za'); $agi->stream_file($soundPrefix.'cislovky/100'); $ret = $agi->stream_file($soundPrefix.'meny/'.$kodMeny.'-pl'); if ($ret['result'] == -1) //pokud nahravka v pl. neexistuje $agi->stream_file($soundPrefix.'meny/'.$kodMeny); } elseif ($listek['mnozstvi'][$index] != 1) { $agi->stream_file($soundPrefix.'za'); prectiCislo($listek['mnozstvi'][$index]); $ret = $agi->stream_file($soundPrefix.'meny/'.$kodMeny.'-pl'); if ($ret['result'] == -1) //pokud nahravka v pl. neexistuje $agi->stream_file($soundPrefix.'meny/'.$kodMeny); } } /** * Precte datum ve stylu "druheho cervence dva tisice dva" * * @param int $timestamp datum v UNIXovem casovem formatu */ function prectiDatum($timestamp){ global $agi, $soundPrefix; $datum = getdate($timestamp); if ($datum['mday'] >= 20) { $desitky = (int) ($datum['mday'] - ($datum['mday'] % 10)); $agi->stream_file($soundPrefix.'datum/d/'.$desitky); $datum['mday'] %= 10; } if ($datum['mday']) $agi->stream_file($soundPrefix.'datum/d/'.$datum['mday']); $agi->stream_file($soundPrefix.'datum/m/'.$datum['mon']); if ($datum['year'] >= 2000) { $agi->stream_file($soundPrefix.'datum/2000'); if ($zbytek=$datum['year']-2000) prectiCislo($zbytek); } else if ($datum['year'] >= 1900) { $agi->stream_file($soundPrefix.'datum/1900'); if ($zbytek=$datum['year']-1900) prectiCislo($zbytek); } } // ********************************************************** // Hlavni program // ********************************************************** set_time_limit(360); error_reporting(0); require('phpagi.php'); $agi = new AGI(); $listekFile = $agi->config['phpagi']['tempdir'].$listekFile; $agi->answer();
- vi -
$agi->stream_file($soundPrefix.'vitejte'); //Test validnosti lokalniho souboru //Tyto testy chvili trvaji - neblokujici hlaska cekejte prosim //obejitim phpagi fwrite(STDOUT,"STREAM FILE ".$soundPrefix."cekejte_prosim \"\"\n"); $cas=time(); $dneska=strtotime('14:35'); $zmena=filemtime($listekFile); if ( file_exists($listekFile) && ( ($cas < $dneska && $zmena >= strtotime('yesterday 14:35')) || ($cas >= $dneska && $zmena >= $dneska) ) && filesize($listekFile) > 50) { //$agi->verbose("Soubor s listkem je aktualni, pouzivam ho."); $soubor = file($listekFile); } else { //$agi->verbose("Stahuji novy listek"); $soubor = file($listekURL); file_put_contents($listekFile,$soubor); } fgets(STDIN,4096); parseListek($soubor,$listek); while (true) { // hlavni menu $volba = menu($menuVolby); sleep(1); if ($volba != 0) { vyhlasKurz($listek,$menuVolby[$volba]); } else { $timestamp = strtotime($listek['datum']); $agi->stream_file($soundPrefix.'kurz_k_datu'); prectiDatum($timestamp); } sleep(1); } $agi->hangup(); ?>
- vii -