System inteligentnej nawigacji sterowanej głosem po serwisie internetowym
-
Upload
jan-klimczak -
Category
Documents
-
view
319 -
download
0
Transcript of System inteligentnej nawigacji sterowanej głosem po serwisie internetowym
Gdańsk, wrzesień 2011
POLITECHNIKA GDAŃSKA
Wydział Elektroniki
Telekomunikacji i Informatyki
Katedra Systemów Decyzyjnych
Imię i nazwisko dyplomanta: Jan Klimczak
Nr albumu: 112006
Forma i poziom studiów: niestacjonarne inżynierskie
Kierunek studiów: Informatyka
Praca dyplomowa inżynierska
Temat pracy: System inteligentnej nawigacji sterowanej głosem po serwisie
internetowym
Intelligent web navigation voice controlled system
Kierujący pracą: prof. dr hab. inż. Zdzisław Kowalczuk
Recenzent:
Zakres pracy: Opracowanie głosowego systemu komunikacji oraz nawigacji po serwisie
internetowym. Projekt zakłada wykonanie portalu testowego, systemu rozpoznawania
i generowania mowy, mózgu systemu oraz postaci Awatara komunikującego się
z użytkownikami oraz kierującego ich po serwisie. System ma być inteligentny w tym znaczeniu,
iż Awatar nie będzie nas tylko słuchał, ale także będzie z nami w dialogu wyrażając się poprzez
swoje emocje. Wykonanie projektu zakłada komunikację w języku angielskim.
Nr raportu:
.......................................................
Imię i nazwisko dyplomanta
1.1.1. OŚWIADCZENIE
Oświadczam, że:
1) niniejszą pracę dyplomową wykonałem samodzielnie,
2) wszystkie informacje umieszczone w pracy uzyskane ze źródeł pisanych oraz informacje
ustne pochodzące od innych osób zostały udokumentowane w wykazie literatury
odpowiednimi odnośnikami.
.................................................
podpis dyplomanta
Spis treści
1.1.1. OŚWIADCZENIE ..................................................................................................... 3
2. CEL PRACY ORAZ ROZPOZNANIE TECHNOLOGII .......................................................... 9
2.1. Wprowadzenie .................................................................................................................. 9
2.2. Założenia ogólne ............................................................................................................... 9
2.3. Przegląd istniejących technologii ................................................................................... 10
2.3.1. Systemy mowy ........................................................................................................ 10
2.3.2. Systemy Java Speech .............................................................................................. 10
2.3.3. Systemy Microsoft Speech ...................................................................................... 11
2.3.4. Systemy rozpoznawania mowy ............................................................................... 11
2.3.5. Systemy syntezy mowy ........................................................................................... 11
2.3.6. Przegląd istniejących systemów mowy ................................................................... 12
2.4. Serwery obsługujące protokół czasu rzeczywistego ...................................................... 14
2.5. Specyfikacja techniczna ................................................................................................. 15
2.6. Podsumowanie ................................................................................................................ 17
3. SPRECYZOWANIE WYMAGAŃ FUNKCJONALNYCH .................................................... 18
3.1. Wprowadzenie ................................................................................................................ 18
3.2. Przeznaczenie systemu ................................................................................................... 19
3.3. Funkcja ........................................................................................................................... 20
3.4. Podsumowanie ................................................................................................................ 21
4. ZASADA DZIAŁANIA ORAZ OPIS SYSTEMU .................................................................. 22
4.1. Wprowadzenie ................................................................................................................ 22
4.2. Architektura systemu ...................................................................................................... 22
4.3. Algorytm działania systemu ........................................................................................... 25
4.3.1. Przygotowanie systemu mowy ................................................................................ 25
4.3.2. Podłączenie klienta .................................................................................................. 26
4.3.3. Wygenerowanie mowy i jej odsłuchanie przez użytkownika ................................. 28
4.3.4. Rozpoznanie wydanej komendy głosowej .............................................................. 31
4.3.5. Mózg ........................................................................................................................ 32
4.3.6. Stan (emocje) ........................................................................................................... 32
4.4. Opis wyników ................................................................................................................. 33
4.5. Struktura oprogramowania ............................................................................................. 33
4.6. Podsumowanie ................................................................................................................ 34
5. REALIZACJA PRACY ............................................................................................................ 35
5.1. Wprowadzenie ................................................................................................................ 35
5.2. Testowanie platformy ..................................................................................................... 36
5.3. Omówienie sposobów rozwiązania problemów ............................................................. 36
5.4. Podsumowanie................................................................................................................ 36
6. MOŻLIWOŚCI ROZWOJU SYSTEMU ................................................................................. 37
6.1. Wprowadzenie ................................................................................................................ 37
6.2. Rozwój klienta ................................................................................................................ 37
6.3. Rozwój systemu generowania mowy ............................................................................. 37
6.4. Rozwój systemu rozpoznawania mowy ......................................................................... 38
6.5. Rozwój postaci Awatara ................................................................................................. 38
6.6. Rozwój Mózgu ............................................................................................................... 38
6.7. Podsumowanie................................................................................................................ 38
7. PODSUMOWANIE ORAZ WNIOSKI Z PRACY .................................................................. 39
7.1. Podsumowanie oraz wnioski z pracy ............................................................................. 39
8. Dodatek 1. Skrótowy opis aplikacji .......................................................................................... 40
Tytuł dyplomu ........................................................................................................................... 40
Cel i przeznaczenie aplikacji ..................................................................................................... 40
Funkcjonalność ......................................................................................................................... 40
Opis realizowanych funkcji ................................................................................................... 40
Lista przykładowych zastosowań .......................................................................................... 41
Szczegółowe opisy działania aplikacji .................................................................................. 41
Architektura sprzętu .................................................................................................................. 42
Architektura oprogramowania .................................................................................................. 42
Opis metody wytwarzania aplikacji .......................................................................................... 44
Założenia i sformułowane zadania ....................................................................................... 45
Specyfikacje ........................................................................................................................... 45
Przygotowanie projektu ........................................................................................................ 45
Prototypowanie i implementacja .......................................................................................... 46
Testowanie ............................................................................................................................ 46
Ocena aplikacji oraz porównanie do innych rozwiązań ....................................................... 46
Wnioski i perspektywy dalszych prac .................................................................................... 46
9. Dodatek 2. Instrukcja dla użytkownika..................................................................................... 47
Przygotowanie do pracy z systemem .................................................................................... 47
Komendy sterujące pracą systemu ........................................................................................ 50
Lokalne ustawienia systemu ................................................................................................. 51
10. Dodatek 3. Instrukcja dla administratora ................................................................................ 52
System znaczników ............................................................................................................... 52
Mapa strony – sitemap .......................................................................................................... 54
Panel administracyjny ........................................................................................................... 54
Edycja generowanych odpowiedzi z systemu – Avatar Speech ........................................... 54
Generator treści systemu ....................................................................................................... 56
Podgląd zawartości wygenerowanej bazy danych na bazie znaczników .............................. 57
Instrukcja wdrożenia systemu ............................................................................................... 57
11. Dodatek 4. Instrukcja dla programisty .................................................................................... 59
Budowa bazy danych ................................................................................................................ 59
Specyfikacja komunikacji z Mózgiem ...................................................................................... 60
Klient (ActionScript) – diagram klas .................................................................................... 61
Serwer Mowy (JavaEE)– diagram klas ................................................................................. 62
System znaczników. .................................................................................................................. 63
Dodanie postaci Awatara .......................................................................................................... 64
Konfiguracja systemu rozpoznawania i generowania mowy .................................................... 66
12. Dodatek 5. Listing kodów źródłowych głównych modułów .................................................. 67
Serwer mowy - SpeechServer ................................................................................................... 67
Klient - SpeechAvatar ............................................................................................................... 82
Model danych – SpeechModel .................................................................................................. 96
Mózg – BrainService ............................................................................................................... 100
Administrator – Administrator ................................................................................................ 104
Portal demonstarcyjny – PortalPG .......................................................................................... 111
13. Bibliografia ............................................................................................................................ 118
2
2.CEL PRACY ORAZ ROZPOZNANIE TECHNOLOGII
2.1. Wprowadzenie
Niniejszy projekt ma za zadanie przegląd oraz scalenie istniejących rozwiązań,
które umożliwiają sterowanie portalem internetowym za pomocą głosu. Użytkownik otrzymuje
możliwość wydawania komend głosowych, a także zapytań odnośnie zawartości portalu. System
w odpowiedzi na komendy generuje komunikaty głosowe, które udzielą odpowiedzi na zadane
pytania. System także może przenosić użytkownika w inne miejsce portalu, o które prosimy
wydając komendy. Do interakcji użytkownika z portalem została utworzona postać
Inteligentnego Awatara (ang. Intelligent Avatar) [1], która umożliwia interakcję głosową
z systemem. Awatar jest wrażliwy na to co i jak mówimy, posiada swoje emocje,
które są uwidocznione podczas komunikacji.
Największym wyzwaniem stojącym przy realizacji projektu jest jego rozproszona praca,
która umożliwia niezależne sterowanie portalem dla wielu użytkowników jednocześnie poprzez
przeglądarkę internetową pracując w trybie on-line1 bez potrzeby zalogowania się do systemu
oraz zastosowany silnik sztucznej inteligencji2 i emocji
3 postaci Awatara, która pamięta
użytkownika i swój stan podczas przeglądania portalu.
2.2. Założenia ogólne
Ze względu na skomplikowanie języka polskiego, czego wynikiem jest praktyczny brak
implementacji tego języka dla technologii mowy (ang. Speech) (rozpoznawania i syntezy mowy)
[2] przedstawiony projekt został wykonany wyłącznie w języku angielskim, co nie wyklucza
dalszej jego rozbudowy o kolejne języki w przyszłości.
Zakłada się kontynuację pracy oraz dalszy rozwój platformy po ukończeniu projektu,
tak więc została zapewniona jego dalsza rozbudowa w sposób mało inwazyjny oraz praktycznie
niezależny dla każdego z modułów.
1 W trybie połączonym z Internetem.
2 Moduł nazwany „Mózgiem” niniejszej platformy.
3 Emocje identyfikują stan Awatara, który jest indywidualny dla każdego użytkownika portalu internetowego.
1. Cel pracy oraz rozpoznanie technologii 10
2.3. Przegląd istniejących technologii
Sporządzony przegląd zawiera zestawienie istniejących technologii, które zostały uwzględnione
w fazie analizy wykonania projektu w obszarze rozpoznawania i generowania mowy.
2.3.1. Systemy mowy
Technologie rozpoznawania i syntezy mowy są od wielu lat rozwijane. Jednak semantyka
i struktura języka ludzkiego są na tyle skomplikowane, iż obecnie pomimo wielu lat rozwoju
nie ma żadnego istniejącego rozwiązania, które było by idealne lub bardzo zadowalające w tym
zakresie4. Wiele projektów zostało albo skomercjonalizowanych
5 i obecnie są dostępne
do zakupienia po wygórowanych cenach mimo, iż nie oferują fantastycznych rezultatów6,
albo zostało zaprzestanych w rozwoju z różnych powodów7.
Pod pojęciem systemu mowy (ang. Speech) wyróżniamy rozwiązania, które w mniejszym
lub większym stopniu umożliwiają rozpoznanie mowy (ang. Speech Recognition) [3]
lub/i jej syntezę, czyli wygenerowanie mowy na podstawie tekstu (ang. Speech Synthesis) [4]
za pośrednictwem własnych bibliotek oraz ich interfejsów API.
W tym obszarze możemy wyróżnić dwa czołowe rozwiązania: system mowy firmy
Microsoft (ang. Microsoft Speech) [5] oraz system mowy Java (ang. Java Speech) [6].
Praktycznie wszystkie obecne rozwiązania na nich bazują. Dzięki nim otrzymujemy dostęp
do framework'u, który umożliwia nam ich swobodne zastosowanie. Oczywiście na rynku istnieją
inne rozwiązania, ale zazwyczaj są one przeznaczone do ściśle określonych celów, a sama
ich licencja oraz brak dostępu do ich kodu uniemożliwia szersze ich wykorzystanie.
2.3.2. Systemy Java Speech
Java Speech API (ang. Java Speech Application Programming Interface, JSAPI) [7]
umożliwia wykorzystanie systemu mowy (ang. Speech) w między-platformowych aplikacjach
opierających się na technologii Java [8]. Dzięki zastosowaniu JSAPI otrzymujemy obsługę
komend i kontroli aplikacji poprzez rozpoznawanie mowy, system obsługujący gramatykę
(ang. Dictation System) [9] oraz syntezer mowy (ang. Speech Synthesis) dla rozwiązań
osobistych lub klasy korporacyjnej. Java Speech API obsługuje rozpoznawanie oraz syntezę
mowy.
Podstawową korzyścią korzystania z technologii Java jest to, iż otrzymujemy rozwiązanie
z otwartym dostępem do kodu (ang. Open Source) [10] oraz pełną zgodność między-
platformową (możliwość uruchomienia aplikacji na wielu platformach, w tym.: Microsoft,
Macintosh oraz Linux, ang. Cross-Platform) praktycznie bez wykonywania dodatkowej pracy
w tym zakresie.
Java Speech API zostało utworzone przez SUN Microsystems, obecnie Oracle
Corporation [11] przy współpracy z przodującymi na tamte czasy firmami8, które zajmowały się
systemami rozpoznawania mowy: Apple Computers, Inc. [12] , AT&T [13], Dragon System,
Inc. [14], IBM Corporation [15], Novell, Inc. [16], Philips [17], Texas Instruments [18].
4 W znaczeniu poprawności rozpoznawania i generowania mowy.
5 Rozwijanych przez firmy i odsprzedawanych po wygórowanych cenach.
6 Pomimo wielu lat rozwoju nie ma obecnie takiego systemu, który zastąpił by człowieka.
7 Większość rozwijanych projektów realizowana była przez uczelnie, jednak wiele z nich zostało zakończonych i nie
jest już rozwijana. 8 Dotyczy to lat 80-90 bieżącego stulecia.
1. Cel pracy oraz rozpoznanie technologii 11
Pierwsza wersja Java Speech API została utworzona i zaakceptowana w 1998 roku. Od tego
też czasu nic się w niej nie zmieniło, nie była także modernizowana. Obecnie tylko różne
jej implementacje są rozwijane.
W 2001 roku rozpoczęto prace nad specyfikacją Java Speech API 2 (ang. JSAPI 2)
dostępną jako zgłoszenie JSR 113 (ang. Java Specification Request: JavaTM Speech API 2.0)
[19]. Pace nad nią trwały przez 8 lat. Jedną z głównych korzyści będzie zgodność
z zaproponowanym frameworkiem mowy dla przeglądarek internetowych przez W3C9
(ang. W3C Speech Interface Framework) [20] poprzez zastosowanie znaczników HTML
bezpośrednio na stronie internetowej oraz kompatybilność z interfejsem programowania systemu
mowy (ang. Speech Application Programming Interface) [21] wywodzącym się z Microsoft’u.
Jednak na JSAPI 2 musimy jeszcze troszkę poczekać ponieważ do dnia dzisiejszego
nie ma praktycznie żadnej działającej implementacji gotowej do wdrożenia i zastosowania.
2.3.3. Systemy Microsoft Speech
Microsoft posiada dobrze rozwinięty system mowy. Na jego stronie „Microsoft Tellme speech
innovation” [22] zostały dobrze opisane technologie przez niego wspierane. Technologie
te pozwalają na rozpoznawanie oraz generowanie mowy (ang. Speech Synthesis
and Recognition) w wielu językach. Dodatkowo za pomocą komend głosowych można sterować
systemem operacyjnym Windows oraz wybranymi aplikacjami. Jednak ograniczenia licencyjne
na wykorzystanie tego systemu mowy wykluczyła wykorzystanie tego rozwiązania
w wykonywanym projekcie.
2.3.4. Systemy rozpoznawania mowy
Od wielu dziesięcioleci ludzie próbują sterować urządzeniami za pomocą głosu. Ze względu
na skomplikowanie ludzkiej mowy pierwsze systemy pozwalały wyłącznie na rozpoznawanie
cyfr. W 1952 roku Bell Laboratories [23] zaprojektował system „Audrey”, który rozpoznawał
cyfry. Dziesięć lat później IBM zaprezentował swoją maszynę „Shoebox” [24],
która rozpoznawała 16 słów w języku angielskim. Następnie w latach 70 bieżącego wieku
powstały pierwsze systemy pozwalające rozpoznać tysiące słów, w latach 80 ta liczba
rozpoznawanych słów została już przekroczona. W latach 90 firma Dragon wprowadziła
do sprzedaży pierwszy produkt rozpoznawania mowy Dragon Dictate w cenie 9000$. Siedem lat
później produkt znacznie udoskonalony o nazwie Dragon NaturallySpeaking umożliwiający
płynne rozpoznawanie mowy o szybkości 100 słów na minutę został udostępniony w cenie
ok. 700$. Od 2000 roku systemy te szczycą się 80 % poprawnością rozpoznawania mowy. Także
od tego czasu możliwości wydawania komend głosowych zostały zaimplementowane
do systemów operacyjnych Microsoft Vista oraz MacOS oraz do wielu telefonów komórkowych
[25].
2.3.5. Systemy syntezy mowy
Synteza mowy polega na zamianie tekstu na mowę (ang. Text To Speech, TTS) [26]. Systemy
te ocenia się na podstawie poprawności i zrozumiałości wygenerowanej mowy, która powinna
być zrozumiała przez człowieka. Z roku na rok systemy te są bardziej dopracowane, jednak
na dzień dzisiejszy daleko jest im do doskonałości.
9 Dokument ten na dzień dzisiejszy znajduje się w wersji roboczej.
1. Cel pracy oraz rozpoznanie technologii 12
2.3.6. Przegląd istniejących systemów mowy
Na rynku istnieje wiele systemów mowy (ang. Speech). Niektóre z nich lepiej sobie radzą,
a niektóre gorzej z rozpoznawaniem i syntezą mowy (ang. Speech Recognition and Synthesis).
Wiele z nich, szczególnie tych dopracowanych występuje w wersji komercyjnej, za którą trzeba
płacić (patrz tab. 1.1.). Większość z tych systemów przedstawia dość wysoki poziom
generowanej i rozpoznawanej mowy. Jednak ze względu na wysokie koszty nie zostały one
uwzględnione przy realizacji niniejszego projektu.
Tab. 2.1. Wybrane komercyjne systemy mowy.
l.p. nazwa cena rodzaj
1. Dragon Medical od 1200$ synteza i
rozpoznawanie mowy
2. Dragon NaturallySpeaking od 30$ za wersję pod
system operacyjny
synteza i
rozpoznawanie mowy
3. Cloud Garden, Talking Java SDK 500$ za serwer (z
ograniczeniami)
synteza mowy
4. Acapela box (Elan Speech Cube) 300€ za godzinę synteza mowy
5. Lumen Vox od 1500$ rozpoznawanie mowy
6. Ivona od 29€ synteza mowy
Dragon Medical [26] oferuje jedne z najbardziej zaawansowanych możliwości rozpoznawania
i syntezy głosu. Jest on przeznaczony dla lekarzy, którzy mogą za jego pośrednictwem szybko
tworzyć raporty medyczne lub przeszukiwać bazy danych. Jest to możliwe dzięki bardzo
dobremu systemowi rozpoznawania tekstu, pełnego zakresu słownictwa medycznego,
technicznego a także sprawności działania, pozwalającej na pracę niemal w czasie rzeczywistym
z systemem.
Drugim według mnie najlepszym systemem mowy jest Dragon NaturallySpeaking [27].
Jest on już przeznaczony dla przeciętnego użytkownika. Pozwala on m. in. na pisanie
oraz edycję dokumentów tekstowych, e-maili, uruchamianie aplikacji i plików, kontrolowanie
myszki oraz pomaga przy wielu innych czynnościach związanych z codzienną pracą
użytkownika z komputerem.
Cloud Garden, Talking Java SDK [28] jest już implementacją JSAPI, która korzysta
z SAPI Microsoftu, tak więc możliwe wykorzystanie jest wyłącznie pod platformą Windows.
Jak nazwa wskazuje jest to SDK (ang. Software Development Kit), które umożliwia praktycznie
dowolną implementację systemu.
Acapela box (Elan Speech Cube) [29] jest generatorem mowy, który pobiera opłaty
w zależności od czasu generacji mowy. Actapela udostępnia nam swój serwer, do którego się
łączymy, generujemy na nim mowę i odbieramy ją z powrotem. Za poprawność pracy całości
odpowiada firma, jednak tylko też ona może dalej rozwijać swój produkt.
Lumen Vox [30] jest firmą, która specjalizuje się w tworzeniu systemów generowania
mowy wysokiej jakości. Ich produkt Lumen Vox zdobył wiele nagród i wyróżnień w kategorii
rozpoznawania mowy. Jednak sama jego cena wskazuje na jego profesjonalne zastosowanie.
Na koniec pozostawiłem nasz rodowity produkt o bardzo wysokiej jakości Ivona [31],
który generuje mowę o bardzo wysokiej jakości. Ivona może poszczycić się generacją bardzo
wysokiej klasy głosu w języku polskim. Posiada ona wiele sposobów licencjonowania, tak więc
dostosowana jest zarówno pod przeciętnego klienta jak i korporacji.
1. Cel pracy oraz rozpoznanie technologii 13
Większość rozwiązań typu Open Source10
przedstawia niestety tylko dobry lub zadawalający
poziom wygenerowanej mowy (patrz tab. 1.2). Wiele z nich korzysta z SAPI Microsoftu,
co dyskwalifikuje je do wykorzystania w projekcie. Tutaj praktycznie tylko dwa rozwiązania
wybijają się spośród dostępnych implementacji: Sphinx oraz FreeTTS. Charakteryzują się one
znacznie lepszą jakością generowanej i rozpoznawanej mowy oraz lepszą dokumentacją
i większą funkcjonalnością. Niewątpliwie w tym sektorze są liderami. Jednak w odróżnieniu
od komercyjnych systemów tego typu są to wyłącznie biblioteki, które wymagają utworzenia
własnej implementacji, gdzie często rozwiązania komercyjne oferują nam kompletny produkt.
Tab. 2.2. Wybrane systemy mowy typu Open Source.
l.p. Nazwa rodzaj
1. Sphinx-4 rozpoznawanie mowy
2. e-Speaking rozpoznawanie mowy (wymaga SAPI)
3. FreeTTS synteza mowy
4. Flite synteza mowy
5. Festival synteza mowy
6. FestVox synteza mowy
Sphinx-4 [32] jest najbardziej zaawansowanym systemem rozpoznawania mowy dostępnym jako
Open Source. Został on w całości napisany w języku Java poprzez współpracę Sphinx group
na Uniwersytecie Carnegie Mellon, Sun Microsystems Laboratories (obecnie Oracle), Mitsubishi
Electric Research Labs (MERL) oraz Hewlett Packard (HP) przy współpracy z Uniwersytetem
California w Santa Cruz (UCSC) i Massachusetts Institute of Technology (MIT).
System ten rozwijany jest od wielu lat oraz posiada możliwości rozpoznawania
pojedynczych komand jak i zarówno całych sekwencji zdań. Umożliwia on podpięcie wielu
modeli języka naturalnego11
w wielu różnych formatach. Posiada bogatą dokumentację
oraz wiele możliwości podpinania sygnału audio.
System e-Speaking [33] jest przykładem zastosowania SAPI Microsoftu, którego
wymaga do pracy. Posiada on ok. 100 wbudowanych komand do interakcji z systemem
operacyjnym. Obsługuje zarówno Windows 2000 jak i Windows XP. Integruje się z pakietem
Microsoft Office.
FreeTTS [34] z pośród wszystkich tu obecnych otwartych platform pod względem
generowania mowy bije konkurencję na głowę. Obsługuje wiele głosów, wraz z możliwością
ich dodawania. Częściowo wspiera standard JSAPI 1.0. Posiada dobrą dokumentację oraz wiele
interfejsów, które pozwalają na integrację tego systemu w innych modułach. Został on w całości
napisany w języku Java, jednak opiera się on na Flite [35] małym, szybkim, pracującym niemal
w czasie rzeczywistym generatorze mowy w całości napisanym w języku C na Uniwersytecie
Carnegie Mellon. Flite wywodzi się z systemu rozpoznawania mowy Festival [36] i projektu
FestVox [37], które nie są już tak funkcjonalne i zaawansowane jak FreeTTS, jednak odznaczają
się wyższą sprawnością.
Możemy wyróżnić tutaj trzeci typ systemów mowy. Mianowicie systemy on-line, do których
możemy podłączyć się za pośrednictwem interfejsów programistycznych (ang. Application
Programming Interface, API). Opierają się one najczęściej na systemach, a w zasadzie
bibliotekach opisanych w tab. 1.2. Są to proste rozwiązania, nie pamiętają stanu, nie posiadają
10
Dostępnych bez opłat z dostępem do kodu źródłowego. 11
Model określa listę słów i sentencji do rozpoznania.
1. Cel pracy oraz rozpoznanie technologii 14
własnej logiki, tylko po prostu generują mowę na bazie podanego tekstu lub rozpoznają
wydawane komendy (patrz tab. 1.3). Najbardziej one pasują do tworzonego tutaj systemu, ale nie
mogą w nim jednak być użyte ze względu na swoje ograniczenia.
Tab. 2.3. Wybrane systemy mowy on-line.
l.p. nazwa rodzaj adres www
1. SpeechAPI rozpoznawanie
mowy
http://speechapi.com/
2. Ivona synteza mowy http://www.ivona.com/en/
3. AT&T Natural Voices® Text-
to-Speech Demo
synteza mowy http://www2.research.att.com/~ttsweb
/tts/demo.php
4. Festvox synteza mowy http://festvox.org/voicedemos.html
5. Festival Text-to-Speech Online
Demo
synteza mowy http://www.cstr.ed.ac.uk/projects/festi
val/onlinedemo.html
6. Demo Cepstral Voices synteza mowy http://cepstral.com/demos/
7. I Online Text to Speech
Synthesizer
synteza mowy http://codewelt.com/proj/speak
8. vozMe synteza mowy http://vozme.com/index.php?lang=en
SpeechAPI jest systemem on-line umożliwiającym użytkownikowi Internetu na wypowiadanie
komend oraz czytanie tekstu, który następnie jest rozpoznawany przez system. Internauta
ma do dyspozycji wyłącznie jedną stronę (właściwie kilka podstron) na której wypowiada
komendy. System ten jest interfejsem programistycznym (ang. API) do którego można podłączyć
się za pomocą różnych technologii, w tym: Java Script, Java12
, PHP, Python, Rubby, Web
Services oraz CLI (ang. Command Line Interface). Tak więc jest to system zamknięty, wysyłajcy
żądanie zawierające nagrany głos do rozpoznania, natomiast w odpowiedzi otrzymujemy
rozpoznaną komendę w formie tekstu. Poprzez obsługę wielu technologii wydaje się być
ciekawym rozwiązaniem na rynku. Niestety w 2011 roku prace nad tym systemem bardzo
zmalały także na chwilę obecną ciężko jest przewidzieć jego przyszłość. Także wykonane testy
podczas rozpoznania, które nierzadko kończyły się niepowodzeniem oraz brak udostępnienia
kodu źródłowego do wykonania potrzebnych poprawek wykluczyły ten system
do wykorzystania w tworzonym projekcie.
Ivona udostępniła raczej demonstracyjny panel zamiany tekstu w głos. Można w nim
podać dość krótki fragment, który zostanie zamieniony w głos. Jednak wyróżnia się ona sporą
listą dostępnych języków oraz bardzo wysoką jakością generowanego dźwięku.
Pozostałe systemy on-line zaprezentowane w tab. 1.3. posiadają bardzo ograniczone,
nazwałbym je podstawowe funkcje generowania mowy, jednak pozwalają one
na jej wygenerowanie w przeglądarce internetowej.
2.4. Serwery obsługujące protokół czasu rzeczywistego
Ponieważ wykonany system ma pracować w Internecie użytkownik nie powinien czekać zbyt
długo na reakcję ze strony systemu. W tym celu do wykonania systemu został wykorzystany
serwer obsługujący protokół czasu rzeczywistego (ang. Real-Time Messaging Protocol, RTMP)
[38] utworzony przez Adobe [39]. W naszym przypadku protokół ten przesyła dane audio w taki
sposób, że użytkownik nie musi czekać na wcześniejszy zapis lub zbuforowanie tych danych,
12
Na chwilę pisania tego rozdziału Java nie była jeszcze dostępna.
1. Cel pracy oraz rozpoznanie technologii 15
tylko bezpośrednio może z tych danych fragment po fragmencie wykorzystać. Zastosowanie
tego protokołu znacznie przyśpieszyło interakcję użytkownika z systemem. Serwery te potocznie
są zwane albo serwerami strumieniowymi albo serwerami czasu rzeczywistego.
Jako, że protokół ten został utworzony przez firmę Adobe także serwery tej firmy
obecnie są najbardziej zaawansowane, dopracowane i stabilne, ale także i kosztują sporo.
W Adobe możemy wyróżnić serwer Flash Media Server [40] oraz LiveCycle DS. [41].
Oba prezentują najwyższą półkę oraz zapewniają wysoką stabilność i dokumentację.
Na drugim horyzoncie znajduje się serwer Wowza Media Server [42], który jest
praktycznie odpowiednikiem tego z Adobe.
Natomiast z wielu rozwiązań typu Open Source praktycznie tylko jedno się liczy,
mianowicie serwer Red5 [43], który wykonany został w technologii Java. Jest to taki mniejszy
brat Adobe Flash Media Server. Na początku 2011 roku został zaprezentowany kandydat
na wersję 1.0. Jednak od tego czasu serwer ten boryka się z wieloma różnymi problemami, które
uniemożliwiły wydanie jego wersji 1.0. Po dopracowaniu myślę, że będzie to solidny serwer,
jednak na dzień dzisiejszy posiada on bardzo skromną dokumentację i wiele elementów trzeba
samemu się domyślać jak je wykonać. Jednak najważniejszą cechą tego serwera jest fakt,
iż występuje on jako Open Source, jest dostępny bez opłat licencyjnych (natomiast jego
konkurenci są wysoko wyceniani) i co najważniejsze obsługuje protokół RTMP.
Serwery tego typu tworzą gniazda na odpowiednich portach (dla RTMP zwykle jest
to port 1935) poprzez które następuje transmisja danych w czasie rzeczywistym. Takie
połączenie w przypadku stron internetowych następuje właśnie poprzez protokół RTMP.
2.5. Specyfikacja techniczna
Ze względu na dalszy rozwój projektu przez uczelnię Politechniki Gdańskiej, Katedrę Systemów
Decyzyjnych cały system został oparty na standardach Open Source. Wyjątkiem są tutaj
platformy technologiczne, np. baza danych, która do zastosowań niniejszego projektu jest
dostępna i zgodna z warunkami licencyjnymi za darmo ze strony firmy Oracle lub server,
który można łatwo podmienić na inny Open Source gdyż, aplikacja została wykonana w pełni
pod standard Java Enterprise Edition.
Obsługiwane systemy operacyjne13
:
Linux Ubuntu 8.04 (server) x3214
Windows15
Macintosh16
.
Wykorzystane serwery:
Server aplikacji: Oracle Web Logic 11g
Server czasu rzeczywistego + java: Red5 RC 1.0.
Wykorzystane standardy Java [44, 45, 46, 47, 48]:
Java 5 EE
EJB 3.0 (ang. Enterprise JavaBean)
13
W kontekście uruchomienia platformy. Odnośnie końcowego użytkownika to może on działać na dowolnym
systemie operacyjnym obsługującym wtyczkę Adobe Flash 10. 14
Po odpowiednim dostrojeniu aplikacja powinna działać i w innych wersjach systemu. Mi osobiście nie udało się
uruchomić systemu pod Linux Fedora Core 14 x64 (problem był z biblioteką xuggler). 15
Potwierdzone w systemie Windows Vista x32. 16
Brak przeprowadzenia testów, ale po odpowiedniej konfiguracji powinno zadziałać.
1. Cel pracy oraz rozpoznanie technologii 16
JPA (ang. Java Persistence API)
EAR (ang. Enterprise Archive)
JSF2 (ang. Java Server Faces 2)
Threads
Entity
Facelets.
Baza danych:
Oracle XE 11g.
Technologie RIA (ang. Rich Internet Application):
Adobe Flex 4.5
AS 3 (ang. Action Script 3)
NetStream
SO (ang. Shared Object)
MXML
Adobe Flash 10.
Technologie Internetowe:
xHTML (ang. Extensible HyperText Markup Language)
CSS (ang. Cascading Style Sheets)
JS (ang. JavaScript)
Sitemap (plik tekstowy).
Technologie rozproszone SOA (ang. Service Oriented Architecture):
SOAP 1.1 (ang. Simple Object Access Protocol)
XML (ang. Extensible Markup Language)
WSDL (ang. Web Services Description Language).
Technologie rozpoznawania mowy:
JSGF (ang. Java Speech Grammar Format).
Wykorzystane bibloteki [55]:
rozpoznanie mowy: Sphinx4
synteza mowy: FreeTTS
konwersja formatów audio: Xuggler
spring Source Framework
parser HTML: JSoup
system logowania: Apache Logger.
Metodologia wytwarzania oprogramowania [49, 50, 51, 52, 53, 54]:
UML 2.0 (ang. Unified Modeling Language)
wzorce projektowe
oprogramowanie obiektowe i rozproszone.
1. Cel pracy oraz rozpoznanie technologii 17
2.6. Podsumowanie
Wykorzystując system mowy w aplikacjach otrzymujemy bardziej naturalny interfejs
użytkownika, który umożliwia łatwiejsze oraz bardziej przyjazne sterowanie aplikacją pomiędzy
użytkownikiem a systemem (w naszym przypadku portalem internetowym).
Możemy wyróżnić dwa główne obszary systemu mowy: rozpoznawanie mowy
(ang. Speech Recognition) oraz synteza mowy (ang. Speech Synthesis). Rozpoznawanie mowy
polega na tym iż komputer słucha co do niego mówimy a następnie konwertuje to na tekst.
Synteza mowy jest odwrotnym procesem, który polega na czytaniu tekstu poprzez
wygenerowany głos ludzki w aplikacji. Często proces ten określany jest jako technologia text-to-
speech (TTS).
Celem pracy jest opracowanie głosowego systemu komunikacji oraz nawigacji
po serwisie internetowym. Projekt zakłada wykonanie portalu testowego, systemu
rozpoznawania i generowania mowy, mózgu systemu oraz postaci Awatara komunikującego się
z użytkownikami oraz kierującego ich po serwisie. System ma być inteligentny w tym znaczeniu,
iż Awatar nie będzie nas tylko słuchał, ale także będzie z nami w dialogu. Ze względu
na skomplikowanie języka i dostępność bibliotek generowania i syntezy mowy wykonanie
projektu zakłada komunikację w języku angielskim.
2
3.SPRECYZOWANIE WYMAGAŃ FUNKCJONALNYCH
3.1. Wprowadzenie
Wykonany system pozwala na komunikację oraz nawigację po utworzonym portalu
internetowym za pomocą głosu. W dialogu uczestniczy postać Awatara, która wyposażona
jest w stan, który docelowo w przyszłości będzie rozszerzony do postaci Dictobot’a [56]
za pośrednictwem odpowiedniej implementacji „Mózgu” systemu. Widok demonstracyjnego
systemu został zaprezentowany na rys 2.1.
2. Sprecyzowanie wymagań funkcjonalnych 19
Rys. 2.1. Widok portalu demonstracyjnego.
3.2. Przeznaczenie systemu
Zaprezentowany system przeznaczony jest dla każdego użytkownika z dostępem do Internetu
ze znajomością języka angielskiego, gdyż obustronna komunikacja odbywa się właśnie w tym
języku.
Praca ta jest pracą rozpoznawczą, przygotowuje ona platformę do dalszego rozwoju
poprzez umożliwienie podziału dalszej pracy na kilka zespołów. Pod tym kątem cały system jest
tworzony i dlatego w pewnych warstwach charakteryzuje się pewnym nadmiarem,
który wydzielił moduły funkcjonalne systemu.
System pokazuje możliwości wykorzystania technologii rozpoznawania i generowania
mowy w połączeniu ze stanem Awatara, który pamięta użytkownika przez całą sesję w której
uczestniczymy w interakcji. Sam Awatar w tym wykonaniu jest częściowo autonomiczny,
jednak został on tak zaprojektowany aby w przyszłości stał się on całkowicie autonomiczny
2. Sprecyzowanie wymagań funkcjonalnych 20
i sam podejmował już niektóre decyzje. Dodatkowo cała zabawa została udostępniona
dla użytkowników bez potrzeby zalogowania się do portalu.
3.3. Funkcja
Wyróżniamy kilka funkcjonalnych stref systemu, które dotyczą zwykłego użytkownika
oraz administratora. Z myślą o wdrożeniu wykonanego systemu do istniejącego portalu
internetowego samodzielnie on generuje i uaktualnia zawartość tego portalu. Ponieważ nie
chcemy także, aby wszystko co na nim się znajduje trafiało do naszego systemu, np. treść
znaczników opisujących tabele lub obrazki na stronie wyposażony on został w moduł filtrowania
zawartości portalu.
Chcemy także, aby cały system szybko działał, tak więc z tego powodu została dodana
baza danych która buforuje zawartość strony. W przeciwnym wypadku użytkownik
był by zmuszony oczekiwać na każdorazowe przeanalizowanie strony portalu i na tej bazie
otrzymywał by odpowiedź, co było by czasochłonne.
Nie chcemy, aby użytkownik musiał logować się do portalu. Powinien po prostu wejść
na stronę i zacząć z niej korzystać bez poświęcania dodatkowego czasu na logowanie się
lub konfigurowanie systemu.
Wymagania względem Awatara:
wyposażony jest w stan (emocje), który jest zapamiętywany i przywracany kiedy
ponownie użytkownik wraca do aplikacji
stan awatara jest normalizowany1 po stronie klienta, niezależnie, nie obciążając
dodatkowo łącza internetowego
wydanie każdej komendy głosowej związane jest z pobraniem stanu Awatara,
gdzie „Mózg” odpowiednio reaguje na ten stan
„Mózg” odpowiada za modyfikację stanu Awatara
stan Awatara został graficznie odzwierciedlony w postaci wykresu słupkowego,
gdzie każdy słupek odzwierciedla inną cechę2
postać Awatara jest animowana w zależności od aktualnego stanu.
Wymagania względem rozpoznawania mowy:
rozpoznawane komendy głosowe:
o „Good Morning” – Awatar wita się
o „Hello” – Awatar wita się
o „How are Yoy ?” - Awatar odpowiada nam jak się czuje i pyta się nas o to samo
o „What is Your Name ?”, „Name” – Awatar odpowiada nam jak się nazywa
o „Read page”, „Read” – Awatar czyta zawartość strony głównej
o „Home”, „People”, „Teachers”, „Specialization”,, „Teaching”, “Research”,
“Laboratories”, “Smart control idea”, – Komendy te przekierowują
do odpowiednich podstron portal demonstracyjnego
o „Next page”, „Next”, i „Previous Page”, „Previous” – Powodują przejście
do następnej i poprzedniej strony w portalu internetowym.
Wymagania względem generowania mowy:
możliwość edycji generowanych komunikatów za pomocą panelu administracyjnego
głos powinien być odpowiednio modulowany w zależności od aktualnego stanu Awatara
1 Wyposażony w mechanizm „zapominania”.
2 W niniejszej implementacji Awatar posiada 4 cechy, a każda z nich przyjmuje wartości od 0 do 200.
2. Sprecyzowanie wymagań funkcjonalnych 21
Wymagania względem mózgu systemu:
autonomiczność, poprzez wykorzystanie technologii SOA umożliwiająca łatwą
jego podmianę nawet w czasie pracy systemu
generowanie odpowiedzi na bazie aktywnego kontekstu (stanu Awatara, adresu strony
żądania i komendy) zawierającej komunikat głosowy, zmianę stanu oraz ew. polecenie
przekierowania w inną część portalu demonstracyjnego.
Wymagania względem portalu demonstracyjnego:
zawiera angielską część informacji z Katedry Systemów Decyzyjnych
zawartość odzwierciedla fragment strony Katedry Systemów Decyzyjnych
zawiera minimum 5 podstron z różną zawartością.
Wymagania względem części administracyjnej:
wygenerowanie zawartości portalu na bazie odpowiednich znaczników
podgląd zawartości bazy danych
edycja komunikatów głosowych wydawanych przez Awatara.
3.4. Podsumowanie
Jest to wzorcowy projekt, który umożliwia sterowanie portalem internetowym za pomocą głosu.
Jego główną funkcją jest możliwość interakcji użytkownika za pomocą głosu poprzez
wydawanie komend głosowych oraz słuchanie i dialog z Awatarem.
Docelowo, jeżeli system ten odniesie sukces będzie on zaimplementowany na portalu
Uczelni, dlatego został on wyposażony we własny system znaczników i parser3,
który na podstawie znaczników przygotowuje odpowiednio bazę danych, która wykorzystywana
jest już przez „Mózg” systemu. W ten sposób została zagwarantowana możliwość
jego implementacji na portalu uczelni w przyszłości.
3 Parser analizuje zawartość stron portalu na postawie specjalnych znaczników. Szczytuje tylko wybraną zawartość
portalu w sposób odpowiednio przygotowany.
3
4.ZASADA DZIAŁANIA ORAZ OPIS SYSTEMU
4.1. Wprowadzenie
Na cały system składa się wiele różnych technologii, które współpracują ze sobą. Ich połączenie
wymusiło utworzenie wielu warstw z wieloma współzależnościami. W celu obsłużenia wielu
użytkowników jednocześnie cały system w kluczowych elementach został podzielony na osobne
wątki, tak aby jeden użytkownik nie musiał czekać na zakończenie operacji związanej z obsługą
innych użytkowników. Całe rozwiązanie zostanie przedstawione w kolejnych podrozdziałach.
4.2. Architektura systemu
Ogólna architektura systemu została przedstawiona na rys. 3.1. Na rysunku możemy zauważyć,
iż cały system jest mocno rozproszony i dzieli się na poszczególne moduły,
które są odpowiedzialne za swoją część. W ten sposób cały projekt został podzielony na
mniejsze części, które mogą być niezależnie zarządzane.
3. Zasada działania oraz opis systemu 23
Rys. 3.1. Ogólna architektura systemu.
Wydzielamy tu część kliencką oznaczoną kolorem błękitnym która jest środowiskiem pracy,
które w danym momencie posiada użytkownik systemu. Część kliencka odpowiada
za komunikację z serwerem, przydzieleniem uprawnień do mikrofonu i głośników,
wygenerowaniem unikalnego identyfikatora dla klienta, który następnie jest wykorzystywany
przez serwer do jego identyfikacji. Także w tej części użytkownik ma kontakt z systemem,
obserwuje wizualnie Awatara, wydaje komendy oraz słucha systemu. Tutaj także znajduje się
aktualna wizualizacja stanu Awatara w postaci wykresu słupkowego oraz konsola logów
z całego systemu, w której użytkownik monitoruje stan platformy w danym momencie.
Po stronie klienta przechowywany jest aktualny stan Awatara unikalny dla każdego
użytkownika, jednak jest on przypisany na stałe do danego komputera, a czasem przeglądarki.
Tak więc po powrocie do systemu w późniejszym czasie na danym komputerze następuje
odczytanie stanu Awatara, który na stałe zachowany jest na dysku użytkownika w postaci
obiektu współdzielonego (ang. Shared Object). Także tutaj użytkownik ma dostęp do ustawień,
które są przechowywane w obiekcie współdzielonym (ang. Shared Object). Ustawienia
te pozwalają na włączenie lub wyłączenie odświeżania stanu Awatara oraz włączenie/wyłączenie
wyświetlania konsoli logowania w portalu internetowym. Ustawienia te powalają zwiększyć
wydajność pracy aplikacji klienta. W ustawieniach można także zdefiniować adres serwera
mowy, który odpowiada za całą dalszą pracę systemu1.
Część serwerowa została oznaczona kolorem zielonym. Fizycznie część ta podzielona
jest na dwa serwery: serwer mowy jest serwerem czasu rzeczywistego, jest to serwer Red5 RC
1.0, który opiera się na platformie Apache Tomcat 6 działający na porcie 8080 dla protokołu
HTML oraz na porcie 1935 dla protokołu RTMP, drugim serwerem jest Oracle WebLogic 11g
1 W końcowej fazie pracy nad systemem opcja ta została domyślnie wyłączona.
3. Zasada działania oraz opis systemu 24
na którym działa „Mózg” systemu, część administracyjna oraz portal demonstracyjny. Sewer
standardowo działa na porcie 7001 dla protokołu HTML.
Serwer mowy jest kluczowym elementem całego systemu, zwyczajowo nazywany
„serwerem” niniejszego systemu. To bezpośrednio z nim komunikuje się klient (w zasadzie
użytkownik systemu). Serwer ten wydziela dalszą interakcję z użytkownikiem. Na serwerze
tym zostały utworzone moduły generowania i syntezy mowy, klienta mózgu, obsługi i konwersji
danych audio przesyłanych w czasie rzeczywistym. Także w tym miejscu znajduje się lista słów
i komend rozpoznawalnych przez system w formacie JSGF (ang. Java Speech Grammar
Format). W tym miejscu mamy konfiguracje systemów rozpoznawania i syntezy mowy
w formatach zdefiniowanych przez specyfikacje bibliotek FreeTTS oraz Sphinx4.
Wykonany serwer mowy posiada architekturę wielo-wątkową2 (ang. Multithreading),
tak więc każde żądanie nowego klienta obsługiwane jest w nowym wątku. Konwersje formatów
danych audio są także wykonywane wielo wątkowo. Rozpoznanie komendy głosowej
z wygenerowaniem odpowiedzi również zachodzi w osobnym wątku.
Wiele operacji w serwerze zachodzi w oparciu o zdarzenia (ang. Events). Dotyczy
to obsługi komunikacji serwera z klientem podczas wymienianych komunikatów, konwersji
plików audio oraz obsługi wymiany danych audio w czasie rzeczywistym.
Serwer mowy wykorzystuje bibliotekę Xuggler do konwersji formatów audio. Biblioteka
ta jest zainstalowana na serwerze w systemie operacyjnym, natomiast sam system korzysta z niej
poprzez referencję. Standardowo serwer Red5 nagrywa dane audio w formacie .flv (ang. Flash
Video) które są następnie konwertowane do formatu .wav (ang. Wave form audio format)
poprzez napisany konwerter wykorzystujący bibliotekę Xuggler, który jest już zrozumiały
dla systemu rozpoznawania mowy. Natomiast generator mowy generuje mowę w formacie .wav,
który jest zbyt duży do przesłania do użytkownika, a także nie jest standardowo obsługiwany
przez serwer Red5 tak więc jest on konwertowany do formatu .mp3 (ang. MPEG-1/MPEG-2
Audio Layer 3), który jest już kilka do kilkunastu razy mniejszy oraz możliwy do odtworzenia
w czasie rzeczywistym przez klienta.
Mózg jest drugim serwerem, który odpowiada za logikę aplikacji. Komunikuje się
on bezpośrednio i wyłącznie z systemem mowy, od którego dostaje komendę użytkownika
w postaci tekstowej, identyfikator klienta oraz stan Awatara oraz zwraca akcję do wykonania
przez klienta wraz z komendą zmiany stanu Awatara lub wykonania przekierowania do innej
strony w portalu internetowym. Komunikacja systemu mowy następuje poprzez klienta Mózgu,
który komunikuje serwer mowy z „Mózgiem” poprzez protokół SOAP przy wykorzystaniu
dokumentu WSDL w technologii rozproszonej SOA. To rozwiązanie umożliwia łatwe
dodawanie nowych implementacji Mózgu oraz przełączanie ich w czasie działania aplikacji.
Mózg na podstawie otrzymanych danych i przetworzeniu posiadanych decyduje o wykonanej
interakcji w portalu internetowym za pośrednictwem serwera mowy, który dodatkowo może
wygenerować komunikaty głosowe dla użytkownika oraz przesłać komunikaty zmiany stanu
Awatara lub zadecydować o wykonaniu przekierowania użytkownika do innej strony na portalu
internetowym.
Oba serwery działają w technologii Java. Wyłącznie Mózg komunikuje się z bazą danych
poprzez fasadę, obiekt typu EJB Stateless Bean wykorzystując Framework JPA i zdefiniowane
źródło danych (ang. Data Source) po stronie serwera oraz mapowanie ORM (ang. Object-
relational maping) dzięki wykorzystaniu encji (ang. Entities). Zastosowaną bazą danych jest
2 Architektura wielo-wątkowa pozwala na równoległą realizację żądań wielu użytkowników równocześnie.
Najczęściej żądania są rozdzielane równomiernie na dostępne procesory i rdzenie dzięki czemu wykonywane
operacje powinny odbywać się szybciej (choć nie zawsze, jeżeli liczba wątków jest zbyt duża) i powinny pozwolić
na wykonanie danej operacji przez nowego użytkownika bez potrzeby oczekiwania na zakończenie wykonywanych
operacji przez innych użytkowników (jednak w praktyce nie zawsze tak jest, jest to zależne od wykonanej
implementacji).
3. Zasada działania oraz opis systemu 25
baza danych Oracle XE 11g, która przechowuje wcześniej przygotowaną zawartość portalu
internetowego oraz treść komunikatów generowanych dla użytkownika.
Nad zapełnieniem bazy danych oraz przygotowaniem w niej danych odpowiada część
Administratora, która szczytuje zawartość portalu na podstawie jego mapy strony (ang. Sitemap)
oraz wcześniej przygotowanym znacznikom mowy (ang. Tags). Moduł ten jest całkowicie
niezależny od pozostałej części systemu, zapewnia on możliwość do podglądu wygenerowanych
danych, wygenerowaniu nowych danych oraz możliwość edycji generowanych komunikatów
dla użytkownika.
Klient łączy się z częścią serwerową za pośrednictwem protokołów RTMP i AMF (ang. Action
Message Format). Za pomocą protokołu RTMP przesyłany jest wyłącznie sygnał audio w czasie
rzeczywistym bez potrzeby przesyłania całego materiału w całości3. Natomiast protokół AMF
służy wyłącznie do wymiany komunikatów pomiędzy serwerem a klientem. Komunikaty
te zawierają informacje typu: stan Awatara, komendy tekstowe przesyłane do serwera w postaci
czytelnego tekstu, informacje zwrotne dla klienta kiedy czeka na niego wiadomość
do odsłuchania, komunikaty logów4 ze strony serwera itp. Klient-serwer komunikują się ze sobą
wyłącznie za pośrednictwem tych dwóch protokołów.
Serwer mowy komunikuje się z Mózgiem za pośrednictwem protokołu SOAP
w technologii rozproszonej SOA. Wyłącznie Mózg komunikuje się z bazą danych. W ten sposób
zostały jasno wyznaczone warstwy systemu.
4.3. Algorytm działania systemu
System posiada budowę modułową, tak więc możemy wyróżnić wiele algorytmów działania
systemu. W poniższym opisie została pominięta obsługa sytuacji awaryjnych, która została
zaimplementowana przez system. Jednak niniejszy rozdział skupia się na poprawnym
i przewidzianym funkcjonowaniu systemu. Opis ten nie obejmuje także opisu sytuacji logowania
wykonywanych operacji, ponieważ są one wykonywane we wszystkich istotnych częściach
systemu5.
4.3.1. Przygotowanie systemu mowy
Do poprawnego działania systemu wymagane jest przygotowanie i inicjalizacja serwera mowy.
Podczas startu serwera inicjalizowany jest system mowy i sam serwer czasu rzeczywistego.
Inicjalizacja serwera mowy polega na załadowaniu i wybraniu głosu (w naszym przypadku jest
to głos męski o nazwie „kevin16”, gdzie 16 oznacza 16kHz próbkowanie, czyli głos
o najwyższej jakości dostępny jako Open Soure), rezerwacji zasobów sprzętowych
wykorzystywanych podczas generowania mowy oraz samo przypisanie generatora do systemu
mowy. Kolejnym kluczowym elementem, który inicjalizowany jest podczas startu serwera jest
to inicjalizacja systemu rozpoznawania mowy. W pierwszej kolejności następuje
skonfigurowanie systemu poprzez załadowanie pliku konfiguracyjnego „config,xml”.
Plik ten przede wszystkim wczytuje listę słów w formacie JSGF (znajdująca się w pliku
„hello.gram”) i komend do rozpoznania przy uwzględnieniu danej rozdzielczości sygnału audio
(w naszym przypadku system został skonfigurowany do wykorzystania najwyższej dostpnej
3 Protokół RTMP w odróżnieniu od innych protokołów naprawdę przesyła dane w czasie rzeczywistym. Inne
protokoły najczęściej „udają”, że przesyłają materiał czasie rzeczywistym, lecz np. przesyłają go w partiach,
a nie bit po bicie jak dzieje się to w przypadku wykorzystania protokołu RTMP. 4 Komunikaty te informują klienta co dzieje się po stronie serwera. W przypadku jakichkolwiek problemów
po stronie serwera klient jest informowany o tym fakcie. 5 Obsługę błędów jak i system logów można prześledzić w załączonym kodzie źródłowym.
3. Zasada działania oraz opis systemu 26
jakości sygnału audio), model akustyczny oraz inne ustawienia wykorzystywane podczas
rozpoznawania mowy. Następnie alokowane są zasoby systemowe niezbędne
do przeprowadzenia procesu rozpoznania mowy. W ten sposób system mowy zostaje
uruchomiony. Bez poprawnego wykonania inicjalizacji powyższych elementów system
nie zadziała w ogóle. Powyższe działania zostają wykonane jednorazowo.
4.3.2. Podłączenie klienta
Interakcja po stronie użytkownika następuje już w momencie otworzenia strony portalu
internetowego. W pierwszym momencie po stronie portalu zostaje wygenerowany unikalny
identyfikator użytkownika, który wykorzystywany jest w połączeniach klienta z serwerem.
Następnie zostaje załadowany klient w technologii Flex poprzez wtyczkę przeglądarki
internetowej Adobe Flash. Klient w momencie startu dokonuje załadowania postaci graficznej
Awatara wykonanej w technologii Adobe Flash oraz odczytuje identyfikator klienta
wygenerowany przez portal internetowy. Potem następuje inicjalizacja klienta, która uwzględnia
utworzenie specjalnej wersji systemu logowania aplikacji klienta, która wysyła logi aplikacji
dla użytkownika za pośrednictwem interfejsu zewnętrznego (ang. ExternalInterface)
za pośrednictwem języka Java Script oraz dzieli logi na podstawowe kategorie. Kolejno
utworzony jest lub wczytany współdzielony obiekt (ang. Shared Object) ustawień danego klienta
oraz otwierany lub nie w zależności od ustawień panel logów po stronie portalu internetowego
(dzięki tej operacji użytkownik ma możliwość na bieżąco śledzenia kolejnych zdarzeń, które
zachodzą w całym systemie oraz w przypadku wystąpienia błędu jest o tym informowany
na bieżąco). W następnej kolejności zostaje zarezerwowany mikrofon do wykorzystania
w systemie (tutaj następuje weryfikacja, czy użytkownik zezwolił aplikacji na wykorzystanie
mikrofonu przez aplikację). W tym momencie następuje wczytanie pozostałych ustawień klienta
zapisanych w obiekcie współdzielonym6. Po wczytaniu ustawień zostaje albo utworzony
standardowy stan Awatara lub zostaje on odtworzony z obiektu współdzielonego. Następnym
krokiem jest przygotowanie klienta do wymiany komunikatów z serwerem, w tym tych
wysyłanych i odbieranych. Po wykonaniu tych wszystkich czynności Awatar zostaje ożywiony,
tzn. zostaje on poddany normalizacji czyli powrotu do stanu normalnego7.
Po przygotowaniu klienta8 następuje połączenie klienta z serwerem. Obsługa połączenia
po stronie klienta następuję w trybie zdarzeniowym (ang. Events). Po wysłaniu żądania
połączenia z serwerem, serwer podejmuje próbę połączenia z klientem w osobnym wątku. Klient
wysyła swój identyfikator do serwera, który jest zapisany w atrybucie klienta po stronie serwera
do jego dalszej identyfikacji. W pierwszym momencie po stronie serwera następuje weryfikacja,
czy łączący się klient czasem nie jest już połączony z serwerem w innym żądaniu9 (np. w innej
zakładce przeglądarki lub w wyniku po prostu przejścia na inną stronę portalu internetowego).
Jeżeli klient jest już połączony to następuje jego scalenie „sesji” z obecnie zapisaną w liście
6 W ten sposób została zapewniona możliwość np. przesłania dodatkowych parametrów do systemu, np. naszego
imienia. Jednak ta wersja systemu nie korzysta z takiego rozwiązania. 7 Normalizacja czyli funkcja zapominania stanu Awatara ze względu na optymalizację wydajności całego systemu
została wykonana wyłącznie po stronie klienta. Zamiast obciążać klienta częstym przesyłaniem informacji o stanie
Awatara np. raz na sekundę, to czynność ta została całkowicie wykonana po stronie klienta. Jeżeli weźmiemy pod
uwagę pracę z wieloma klientami jest to znaczne odciążenie zarówno klienta jak i serwera odnośnie dodatkowej
obsługi każdego takiego żądania kosztem większego skomplikowania systemu. 8 Przedstawione przygotowanie klienta występuje po każdorazowym odwiedzeniu strony w portalu internetowym.
9 Tutaj sytuacja trochę się komplikuje ponieważ połączenie klient-serwer w prezentowanym systemie nie jest
połączeniem sesyjnym. O zapewnieniu trwałości połączenia system musi sam zdecydować, nie dzieje się
to automatycznie ze względu na to iż aplikacje Flex są bezstanowe w momencie przejścia do innej strony
internatowej lub jej przeładowania.
3. Zasada działania oraz opis systemu 27
klientów aktualnie połączonych10
, w przeciwnym razie zostaje klient dodany do listy nowych
klientów aktualnie połączonych z systemem. W przypadku sukcesu połączenia i w zależności
od typu połączenia (nowe lub wznowione) w osobnym wątku zostaje wygenerowana wiadomość
powitalna dla klienta11
. Na koniec zostaje wysłana wiadomość powitalna dla klienta, która jest
wiadomością kontrolną czy funkcjonuje komunikacja dwustronna w obszarze klient-serwer.
Dopiero w tym momencie klient odbiera potwierdzenie połączenia z serwerem i ustawia
swój status połączenia na połączony12
. Jeszcze pozostało zestawienie połączenia gniazda (ang.
Socket), właściwie strumienia danych po protokole RTMP.
W ten sposób klient został połączony i przygotowany do dalszego użycia.
Przebieg algorytmu podłączenia klienta:
1. otworzenie strony internetowej
2. wygenerowanie unikalnego identyfikatora użytkownika
3. załadowanie klienta poprzez wtyczkę flash
4. wczytanie postaci graficznej awatara poprzez klienta
5. odczytanie wygenerowanego identyfikatora poprzez klienta
6. utworzenie systemu logowania po stronie klienta
7. wczytanie lub utworzenie obiektu SO z ustawieniami klienta
8. inicjalizacja mikrofonu jeżeli zezwolono
9. wczytanie ustawień z obiektu SO
10. wczytanie lub utworzenie stanu Awatara
11. przygotowanie komunikacji klient-serwer
12. ożywienie Awatara w proces normalizacji
13. połączenie klienta z serwerem wraz z wysłaniem identyfikatora klienta w nowym wątku
14. zapisanie identyfikatora klienta po stronie serwera
15. weryfikacja czy klient nie jest już połączony z serwerem
16. dodanie lub przekierowanie klienta do listy połączonych z serwerem
17. wygenerowanie wiadomości powitalnej dla klienta w nowym wątku
18. wysłanie wiadomości powitalnej do klienta z informacją, iż został on już połączony
19. serwer wysyła wiadomość do klienta, iż czeka na niego wiadomość do odsłuchania
20. zestawienie połączenia RTMP pomiędzy klientem i serwerem.
10
Połączenie klienta determinowane jest po długości czasu życia sesji po stronie portalu internetowego według
wygenerowanego identyfikatora klienta. 11
Algorytm generowania mowy został opisany w osobnym podrozdziale. 12
W rzeczywistości status connectionStatus otrzymuje wartość true.
3. Zasada działania oraz opis systemu 28
Rys. 3.2. Wykorzystanie wątków podczas inicjalizacji klienta.
Rysunek 3.2 przedstawia sposób wykorzystania wątków podczas inicjalizacji klienta. P0 oznacza
klienta, T0 reprezentuje podłączenie klienta do serwera, każdy klient zostaje obsłużony
w osobnym wątku, następnie T1 oraz T2 przedstawiają wygenerowanie wiadomości powitalnej,
która generowana jest także w osobnych wątkach, aż w końcu wszystkie te komunikaty wracają
z powrotem do klientów.
4.3.3. Wygenerowanie mowy i jej odsłuchanie przez użytkownika
System umożliwia proste wygenerowanie mowy na podstawie przekazanego tekstu lub bardziej
zaawansowane przy wykorzystaniu obiektu złożonego zawierającego stan Awatara.
W zależności od implementacji Mózgu przy wykorzystaniu obiektu złożonego możemy
otrzymać inny wynik wygenerowanej mowy w zależności od aktualnego stanu Awatara a także
system może przekierować nas w inne miejsce w portalu internetowym. Przy wykorzystaniu
prostego tekstu zawsze otrzymamy wygenerowaną mowę do odsłuchania. Ze względu na to
iż obiekt złożony rozszerza wykorzystanie prostego tekstu podczas wygenerowania mowy,
w tym rozdziale tylko ten algorytm zostanie opisany. Schemat wygenerowania komendy
głosowej został przedstawiony na rys. 3.3.
3. Zasada działania oraz opis systemu 29
Rys 3.3. Wygenerowanie odpowiedzi na podstawie komendy głosowej.
Na początku zostaje utworzony obiekt zapytania, który zawiera komendę tekstową
oraz stan Awatara13
. Komenda tekstowa jest tekstem, który ma posłużyć do wygenerowania
mowy. Poprzez klienta obiekt zapytania jest przekazany do Mózgu za pośrednictwem protokołu
SOAP. W samym mózgu zapytanie jest przetwarzane14
. W naszym systemie następuje
porównanie nadesłanej komendy głosowej czy jest ona rozpoznawana przez Mózg. W zależności
od rozpoznanej lub nie komendy następuje przygotowanie odpowiedzi zwrotnej, która zawiera
tekst odpowiedzi pobrany z bazy danych oraz wartości zmiany stanu Awatara w zależności
od kontekstu15
w wartościach względnych16
a także opcjonalnie informację o wymaganym
przekierowania użytkownika na inną stronę portalu internetowego.
Odpowiedź ta jest zwracana z Mózgu do serwera mowy, który na jej podstawie
przygotowuje obiekt odpowiedzi dla klienta, następnie odpowiedź ta przesyłana jest do klienta
i modyfikuje na tej podstawie stan Awatara lub przekierowuje użytkownika na inną stronę
w zależności czy to przekierowanie miało nastąpić, czy też nie.
Równolegle na podstawie wcześniej zapisanego identyfikatora klienta, który zgłosił
żądanie na wygenerowanie mowy rozpoczyna się proces generowania mowy. W tym miejscu
od razu na wstępie zostaje wygenerowany unikalny numer pliku globalny dla całego serwera
mowy, który będzie wykorzystany do zapisania wygenerowanej mowy poprzez wykorzystanie
13
Zwyczajowo stan Awatara jest bezpośrednio pobierany od klienta przed samym wysłaniem zapytania do Mózgu.
W celu pobrania odpowiedniego stanu Awatara klient jest wcześniej odpowiednio identyfikowany, zaś samo
pobranie stanu zostaje wykonane za pośrednictwem protokołu AMF. 14
W zależności od implementacji Mózgu wynik może być zupełnie odmienny. 15
Kontekst jest definiowany i pobierany przez Mózg systemu. 16
Są to wartości, które są dodawane do stanu Awatara (mogą być także wartościami ujemnymi).
3. Zasada działania oraz opis systemu 30
własnej implementacji modułu zapisu wygenerowanej mowy do pliku. Z syntezatora mowy
zostaje pobrany odtwarzacz, który zostaje przekierowany do pliku. Zostaje wygenerowana
mowa, która zostaje zapisana do pliku .wav, który jeszcze nie nadaje się do przesłania do klienta.
W tym miejscu nowo wygenerowany plik zostaje przekonwertowany na podstawie
implementacji konwertera biblioteki Xlugger z formatu .wav do formatu .mp3. Konwersja
ta zawsze przebiega w nowym wątku. Konwerter sprawdza czy na podstawie
przekonwertowanego pliku ma nastąpić rozpoznanie komendy i wykonanie akcji17
. W przypadku
zwykłej konwersji do tej czynności nie dochodzi wcale. Jest ona zarezerwowana dla obsłużenia
komend głosowych wydanych bezpośrednio po stronie klienta.
Po wygenerowaniu pliku mowy i przekonwertowaniu go do formatu umożliwiającego
jego odtworzenie w czasie rzeczywistym zostaje wysłana komenda odtwórz wiadomość w raz
z nazwą pliku do odtworzenia dla klienta zgłaszającego prośbę o wygenerowanie mowy.
W tym momencie klient otrzymawszy taką wiadomość, sprawdza czy połączenie
z serwerem i gniazdem jest już gotowe. Jeżeli jest już gotowe to następuje odtworzenie
wygenerowanej mowy w czasie rzeczywistym. W przeciwnym wypadku zgłoszenie zostaje
zapisane po stronie klienta, który ponawia 5-cio krotnie próbę otworzenia oczekującej
wiadomości co 0,5s. Po tym czasie próba odtworzenia wiadomości zostaje zaniechana,
a klientowi zostaje przekazany komunikat o błędzie w konsoli logowania.
Przebieg algorytmu generowania mowy:
1. utworzenie obiektu zapytania zawierającego tekst mowy do wygenerowania oraz stan
awatara (stan pobierany jest od klienta tuż przed samym wysłaniem żądania do Mózgu)
2. przesłanie zapytania do Mózgu za pośrednictwem protokołu SOAP
3. rozpoznanie nadesłanej komendy
4. pobranie tekstu odpowiedzi z bazy danych
5. obliczenie wymaganej zmiany stanu Awatara
6. przygotowanie strony przekierowania o ile zajdzie taka potrzeba
7. wygenerowanie odpowiedzi zwrotnej
8. przekazanie odpowiedzi z Mózgu do serwera mowy
9. utworzenie komunikatu zwrotnego dla klienta
10. przesłanie komunikatu zwrotnego do klienta
11. zmiana stanu Awatara po stronie klienta
12. przekierowanie użytkownika na inną stronę o ile zajdzie taka potrzeba
13. wygenerowanie unikalnego w skali serwera mowy identyfikatora pliku
14. utworzenie pliku .wav zawierającego odpowiedź audio dla klienta
15. w nowym wątku następuje konwersja pliku .wav na plik .mp3
16. opcjonalnie następuje tu wykrycie komendy oraz wykonanie akcji na tej podstawie
17. wysłanie komendy odtwórz oczekującą wiadomość do klienta
18. klient weryfikuje czy podłączenie RTMP z serwerem jest już gotowe
19. następuje 5-cio krotna próba odtworzenia oczekującej wiadomości w czasie
rzeczywistym.
17
Jest to uniwersalne rozwiązanie pozwalające usprwanić proces dalszej analizy i obróbki uzyskanego materiału
audio. Rozpoznanie komendy i wykonanie akcji występuje wyłącznie w momencie, gdy komenda ta została
zgłoszona bezpośrednio ze strony klienta poprzez protokół czasu rzeczywistego RTMP. W innym wypadku nie jest
ona w ogóle wykorzystywana.
3. Zasada działania oraz opis systemu 31
Rys. 3.4. Wykorzystanie wątków podczas generowania mowy.
Rysunek 3.4 przedstawia sposób wykorzystania wątków podczas generowania mowy.
P0 oznacza zgłoszenie pliku oczekującego na konwersję, T0 reprezentuje konwersję pliku .wav
na .mp3. Każdy plik zostaje obsłużony w osobnym wątku, następnie następuję przesłanie
odpowiedzi iż pliki zostały przekonwertowane dla swoich „zleceniodawców”.
4.3.4. Rozpoznanie wydanej komendy głosowej
Rozpoznanie komendy głosowej rozpoczyna się w momencie wciśnięcia i przytrzymania
przycisku „Speak” w kliencie przy postaci Awatara. W tym momencie następuje nagranie
materiału jeszcze audio/video na serwerze18
w czasie rzeczywistym pod wysłaną nazwą klienta.
Podczas tej operacji serwer przechwytuje materiał audio/video i zapisuje go na dysku komputera.
W momencie odpuszczenia przycisku przez użytkownika jest wysyłany komunikat o ukończeniu
nagrywania do serwera. Teraz w nowym wątku po stronie serwera następuje konwersja pliku .flv
do pliku .wav za pośrednictwem implementacji bibloteki Xuggler19
a następnie rozpoznanie
komendy i wykonanie akcji opisanej w poprzednim podrozdziale.
Przebieg algorytmu rozpoznania mowy:
1. rozpoczęcie procesu rozpoznania mowy następuje z chwilą wciśnięcia przycisku „Speak”
po stronie klienta
2. następuje nagrywanie materiału audio/video po stronie serwera w czasie rzeczywistym
3. odpuszczenie przycisku „Speak” po stronie klienta powoduje wysłanie komunikatu
do serwera o zakończeniu procesu nagrywania
18
Serwer Red5 umożliwia wyłącznie nagrywanie materiału audio/video w czasie rzeczywistym. 19
Operacja ta została opisana w rozdziale „Wygenerowanie mowy i jej odsłuchanie przez użytkownika”.
3. Zasada działania oraz opis systemu 32
4. w nowym wątku rozpoczyna się konwersja formatu .flv do pliku .wav
5. teraz następuje rozpoznanie komendy z otrzymanego pliku .wav
6. na tej podstawie wysyłana jest wiadomość do Mózgu a następnie występują czynności
opisane w poprzednim podrozdziale.
Wykorzystanie wątków w tym procesie jest analogiczne jak przy procesie generowania mowy.
4.3.5. Mózg
System ten został wyposażony w bardzo prostą implementację Mózgu, która porównuje
otrzymane komendy do tych zapisanych we własnej pamięci i na tej podstawie wykonuje
przypisane operacje. Mózg otrzymuj kontekst danego użytkownika składający się z chwilowego
stanu (emocji) Awatara, aktualnej strony WWW, z której przyszło dane żądanie oraz
z komunikatu tekstowego. Na bazie kontekstu i rozpoznanej komendy z komunikatu tekstowego
generowana jest na sztywno odpowiedź, która zawiera pobraną z bazy danych komendę
głosową, polecenie i adres przekierowania użytkownika do innej strony internetowej oraz
zmianę stanu (emocji) Awatara. Zmiana stanu Awatara w tej implementacji Mózgu polega
na jego zmianie w losowej wartości zmiany w określonym zakresie20
.
4.3.6. Stan (emocje)
Awatar wyposażony jest w samoistną świadomość wyposażoną w mechanizm „zapominania”,
który działa wyłącznie po stronie klienta i doprowadza stan (emocje) do stanu
znormalizowanego. Stan Awatara przechowywany jest na komputerze klienta i zapamiętywany
jest nie tylko poprzez daną sesję użytkownika (które de facto nie wymaga chociażby
zalogowania się do systemu), ale pamiętany jest od czasu ostatnich odwiedzin portalu
internetowego. Pamięć ta opiera się na mechanizmie Shared Object platformy Adobe Flash,
który w uproszczeniu21
jest odpowiednikiem pliku ciasteczka (ang. cookie).
Na bazie aktualnego stanu Mózg jest w stanie wygenerować inną odpowiedź,
która spowoduje odmienne zachowanie się Awatara oraz zmieni modulację jego głosu22
.
Graficzna postać Awatara jest animowana23
na bazie stanu w sposób ciągły (patrz rys. 3.5),
natomiast komenda głosowa w całości uwzględnia stan z momentu jej wygenerowania.
Rys. 3.5. Animacja postaci Awatara w zależności od stanu (emocji).
20
Dzięki aktualnej implementacji możemy określić zachowania Awatara jako nieobliczalne. Jednak jest to tylko
i wyłącznie kwestią implementacji Mózgu. 21
Tak naprawdę Shared Object jest obiektem o wiele bardziej zaawansowanym niż ciasteczko. 22
Modulacja zawiera zmianę szybkości mówienia, częstotliwości oraz wysokości głosu. 23
Animacja uwzględnia powiększenie oraz pomniejszenie źrenic, zmianę kolorystyki twarzy oraz zmianę tępa
animacji mowy w momencie, gdy Awatar nam odpowiada.
3. Zasada działania oraz opis systemu 33
4.4. Opis wyników
Pierwsze testy integralne całego systemu wykazały jego stabilną pracę. Jednak wykorzystane
biblioteki rozpoznawania i syntezy mowy, pomimo iż zostały wykorzystane ich najbardziej
dopracowane biblioteki w wersji Open Source pozostawiają jeszcze wiele do życzenia. Jakość
wygenerowanej mowy nie jest wysokiej jakości, a sam system rozpoznawania mowy
charakteryzuje się wysokim progiem błędów, przez co wydawane komendy są źle
interpretowane. Biblioteki te są stale rozwijane, jednak tempo ich rozwoju nie jest zbyt wielkie,
lecz gdy pojawią się ich lepsze odpowiedniki będzie można je podmienić w systemie.
4.5. Struktura oprogramowania
Tak jak zostało to już wcześniej wielokrotnie napisane cały system został wykonany jako zbiór
wielu modułów wymienionych w tab. 3.1. Każdy moduł jest niezależnym projektem. Wszystkie
kluczowe moduły posiadają oddzielne aplikacje testujące.
Tab. 3.1. Moduły systemu.
l.p. moduł technologia opis
1. Administrator Java, JSF, EJB, JSoup,
HTML, JAAS
część administracyjna
2. AdministratorTest Java, Junit testy jednostkowe
3. Avatar Flash, AS postać Awatara
4. BrainService Java, EJB, WSDL,
WSDL, Web-Service,
SOA
Mózg
5. BrainServiceClient Java, Web-Service,
SOA
klient Mózgu
6. BrainServiceClientTest Java, Junit testy jednostkowe
7. PortalPG Java, JSF, HTML, JS,
CSS
portal demonstracyjny
8. SpeechAvatar Flex, AS, MXML,
RTMP, AMF
klient
9. SpeechModel Java, JPA, EJB, Entity,
ORM
odwzorowanie bazy danych,
model danych oraz fasada
10. SpeechModelTest Java, Junit testy jednostkowe
11. SpeechServer Java, Spring, Injection,
RTMP, AMF
serwer mowy
12. SpeechServerTest Java, Junit testy jednostkowe
13. Baza danych Oracle, SQL baza danych
Następujące moduły składają się na systsem:
Administrator
BrainService
PortalPG
SpeechModel
3. Zasada działania oraz opis systemu 34
4.6. Podsumowanie
Jak zostało to przedstawione w tym rozdziale architektura systemu jest mocno rozproszona.
Praktycznie każdy moduł może istnieć niezależnie, jednak wszystkie moduły razem wzięte
tworzą pełną platformę. Taka budowa zapewnia dobrą rozbudowę systemu.
4
5.REALIZACJA PRACY
5.1. Wprowadzenie
W momencie realizacji niniejszego projektu posiadałem już wieloletnie doświadczenie
w programowaniu głównie systemów internetowych w różnych technologiach, w tym systemów
rozproszonych oraz korporacyjnych. Dzięki temu swobodnie mogłem skupić się na sposobie
implementacji technologii Speech (rozpoznawania i syntezy mowy) dla celów komunikacji
użytkownika z systemem za jej pośrednictwem w portalu internetowym. Technologia ta jest
dla mnie pewnym nowym wyzwaniem, które zamierzam zrealizować. Na chwilę obecną nie ma
żadnych gotowych samouczków czy pomocy naukowych które opisały by sposób wykonania
takiego systemu. Do dyspozycji mamy odrębne elementy opisane osobno, które należy zmusić
do współpracy ze sobą w pewnych określonych warunkach (w naszym przypadku chodzi
o zastosowanie klient-serwer, z wykorzystaniem dodatkowych zasobów po stronie klienta co jest
dodatkowym utrudnieniem).
Realizacja całego projektu przebiegała przy ciągłej współpracy i pod nadzorem Pana
prof. dr hab. inż. Zdzisław Kowalczuk, który wyznaczył kierunek wykonanej pracy oraz czuwał
nad tym aby nie był to projekt zamknięty, ale mógł być kontynuowany przez kolejne lata
na uczelni.
Wykonanie pracy zostało podzielone na kilka kluczowych etapów opisanych poniżej.
Wyszczególnione etapy zostały wyodrębnione na bazie poszczególnych funkcji bądź założeń,
które nie koniecznie zostały wykonane w opisanej kolejności, a często były tworzone
równorzędnie.
Etapy realizacji projektu:
1. zapoznanie się z technologią Speech oraz AI
2. wykonanie systemu analizy serwisu
3. system generowania oraz rozpoznawania mowy
4. silnik AI wraz z inicjacją "świadomości" bota oraz z implementacją 4 stanów emocji
5. stworzenie prostej graficznej postaci Awatara komunikującej się z silnikiem AI
4. Realizacja pracy 36
6. utworzenie demonstracyjnego serwisu z implementacją postaci Awatara
7. przygotowanie odpowiedniej dokumentacji.
5.2. Testowanie platformy
Podstawową techniką testowania systemu jest wykorzystanie jednostek testowych, które
weryfikują cząstkowe działanie systemu. System powstawał krok po kroku, z małego rozwinął
się w duży wykonując testy użytkownika podczas jego działania. Skala złożoności i zależności
systemu jest tak duża, że do pomocy został wykonany specjalny system logowania, który
informuje użytkownika o pracy całego systemu umożliwiając wykrycie różnego rodzaju
dolegliwości i problemów w systemie, którego przetestowanie jest o tyle skomplikowane,
iż opiera się na mowie, stanie Awatara, który nie bazuje na sesji a dodatkowo rozproszony jest
na wiele pod stron, niezależnych żądań przekierowań, ale jednak tworzących pewną całość,
która była testowana poprzez użytkownika systemu.
5.3. Omówienie sposobów rozwiązania problemów
Głównym problemem, który pojawił się na początku było spasowanie i dobór istniejących
technologii do wykonanego projektu. Nadrzędnym celem jest uzyskanie wysokiej jakości
produktu przy wykorzystaniu technologii Open Source, tak aby projekt mógł być dalej rozwijany
przez uczelnie. Tak więc po wybraniu kluczowych technologii rozpoznawania i syntezy mowy
nastąpiło pasowanie do nich technologii pomocniczych. Tutaj pojawiły się już pierwsze
ograniczenia, które wskazały jakie formaty danych są obsługiwane przez powyższe technologie.
W tym momencie nastąpiło poszukiwanie technologii wspierających
Możliwość pracy rozproszonej dodatkowo komplikuje sam fakt dostępu do zasobów
komputera użytkownika (mikrofonu i głośników), który domyślnie jest zablokowany. W tym
celu została wykorzystała wykorzystana technologia Adobe Flex, która daje dostęp
do wymaganych zasobów.
5.4. Podsumowanie
W informatyce mówi się, że po wykonaniu 80% projektu do jego ukończenia pozostało
pozostałe 90%, co miało miejsce także i w tym projekcie. Projekt był nowatorski, wiele się
z niego nauczyłem, bardzo mnie rozwinął, pokonałem w nim wiele przeszkód i rozwiązałem
wiele problemów.
5
6.MOŻLIWOŚCI ROZWOJU SYSTEMU
6.1. Wprowadzenie
We wczesnym etapie planowania projektu jednym z jego głównych elementów było wymaganie,
które mówiło iż projekt powinien być dalej rozwijany po jego ukończeniu. Zostało jasno
powiedziane, iż należy go tak przygotować, aby potem można było go w prosty sposób rozwijać
i podzielić jego dalszą pracę nad nim nad poszczególnymi modułami. I tak też się stało.
6.2. Rozwój klienta
System można wzbogacić o dodatkowe możliwości konfiguracyjne dla użytkownika.
Użytkownik mógłby podać swoje imię lub inne dane, które były by uwzględniane podczas
dalszej interakcji z systemem. Można by dać możliwość wyboru postaci Awatara, głosu przez
niego używanego oraz np. możliwości zmiany Mózgu, która charakteryzowała by się całkowicie
odmienną pracą systemu.
6.3. Rozwój systemu generowania mowy
System generowania mowy można wyposażyć w dodatkowe głosy, które mogą być przełączane
po stronie klienta. Istnieje także możliwość, aby utworzyć własne głosy, jest to jednak zadanie
bardzo pracochłonne i obarczone dużym ryzykiem niepowodzenia.
Ogólnie jest możliwość dodania głosów w innych wersjach językowych, jednak w tym
miejscu problemem może okazać się brak możliwości dostosowania systemu rozpoznawania
mowy w tych językach1.
Istnieje możliwość doposażenia systemu w nowe dodatkowe możliwości sterujące
generacją głosu w rodzaju możliwości sterowania prędkością, wysokością i częstotliwością
1 Problem ten dotyczy sytuacji dnia dzisiejszego.
5. Możliwości rozwoju systemu 38
głosu. Także w tym miejscu można przygotować obsługę poprawnej wymowy znaków
specjalnych, skrótów i innych zapisów specjalnych.
6.4. Rozwój systemu rozpoznawania mowy
System rozpoznawania mowy można wyposażyć w kolejne komendy jeszcze bardziej
zaawansowane oraz o większą ich ilość. Teoretycznie istnieje możliwość nauczenia systemu
do rozpoznawania nowych nazw jeszcze nie zdefiniowanych w istniejących słownikach, co jest
bardzo czasochłonne i obarczone jest dużym ryzykiem niepowadzenia.
Można tutaj pomyśleć o implementacji mechanizmów rozpoznawania ciągłej mowy.
6.5. Rozwój postaci Awatara
Postać Awatara można dopracować graficznie, dopieścić jego animację i reakcję na zmianę
stanu. Można by stworzyć pakiet postaci Awatarów, z których użytkownik mógłby wybrać ten
z którym chciał by się utożsamić.
6.6. Rozwój Mózgu
Rozwój Mózgu daje najwięcej możliwości ponieważ może całkowicie zmienić zachowanie
systemu w danej sytuacji. Można by dodać do Mózgu informacje o danych poczynaniach danego
klienta, np. w celu usunięcia głupich odpowiedzi w nieskończoność typu „Jak się nazywasz?”,
gdzie po kilku takich pytaniach Awatar mógłby zwrócić uwagę klientowi, iż pytał się już
o to wielokrotnie.
Co najważniejsze można by pomyśleć o implementacji autonomicznego umysłu Awatara,
który w określonych warunkach mógłby samodzielnie podejmować decyzję, jak ma to miejsce
np. w Dictobot’cie oraz wiernie zaimplementować jego stan.
6.7. Podsumowanie
Na rynku obecnie nie ma zbyt wiele (o ile istnieją) takich lub podobnych systemów. Warto
podjąć kontynuację niniejszego projektu oraz zbadać i przetestować nowe możliwości, które on
ze sobą niesie.
6
7.PODSUMOWANIE ORAZ WNIOSKI Z PRACY
7.1. Podsumowanie oraz wnioski z pracy
Praca nad systemem była wymagająca, rozwojowa, ale i wciągająca. System który powstał jest
niekonwencjonalny, przełamał wiele barier technologicznych oraz pokazał praktyczne
zastosowanie wielu różnych technologii.
W chwili obecnej nie udało mi się znaleźć żadnego odpowiednika niniejszego systemu1
lub systemu, który w dużej części odpowiadał by obecnej realizacji. Do jego realizacji
poświęciłem naprawdę wiele czasu, wiele nocy nie przespałem, kilka tygodni wolnego z pracy
także zostało spożytkowane w tym celu. Sporą część realizacji zawdzięczam Panu prof. dr hab.
inż. Zdzisław Kowalczuk, który nadał kierunek i pokierował pracą w tę stronę, w której praca
obecnie się znajduje a także poświęcił wiele czasu nad jej realizacją, za co jestem bardzo
wdzięczny.
Wykonanie systemu było dla mnie niezwykle rozwijające, nauczyłem się wiele z samych
technologii informatycznych, ale także poznałem elementy, które nadają „życia” temu
komputerowemu projektowi. Był to czas, którego nie żałuję iż wykorzystałem go w taki a nie
w inny sposób.
Projekt obecnie znajduje się na wstępnej rozwojowej drodze, tak więc nadaje się
on do podjęcia jego rozbudowy i dopracowania, tak aby był w stanie godnym przedstawienia
go szerszemu gronu odbiorców.
1 Mogę z wielką pewnością stwierdzić, iż taki odpowiednik nie istnieje wcale.
40
8.Dodatek 1. Skrótowy opis aplikacji
Tytuł dyplomu
System inteligentnej nawigacji sterowanej głosem po serwisie internetowym.
Cel i przeznaczenie aplikacji
Opracowanie głosowego systemu komunikacji oraz nawigacji po serwisie internetowym. Projekt
zakłada wykonanie portalu testowego, systemu rozpoznawania i generowania mowy, mózgu
systemu oraz postaci Awatara komunikującego się z użytkownikami oraz kierującego ich
po serwisie. System ma być inteligentny w tym znaczeniu, iż Awatar nie będzie nas tylko
słuchał, ale także będzie z nami w dialogu wyrażając się poprzez swoje emocje. Wykonanie
projektu zakłada komunikację w języku angielskim.
Funkcjonalność
Opis realizowanych funkcji
Praca w Internecie bez potrzeby zalogowania się
Równoczesna obsługa wielu użytkowników w osobnych wątkach
Stan Awatara wyposażony w 4 niezależne wartości
Pamięć stanu Awatara
Rozpoznawanie wybranych komend głosowych
Generowanie mowy z uwzględnieniem aktualnego stanu Awatara
Odtwarzanie oraz nagrywanie mowy w czasie rzeczywistym
Konwersja formatów: FLV -> WAV -> MP3 w osobnych wątkach
Funkcjonalny portal demonstracyjny
Panel administratora
Niezależny Mózg systemu generujący mowę, odpowiedź i przekierowanie w zależności
od kontekstu jako Web-Service
Wizualizacja postaci Awatara, który graficznie przedstawia swój stan
Animacja ruchu ust Awatara podczas jego mowy
Prosta implementacja modulacji głosu Awatara w zależności od jego stanu
Możliwość edycji generowanych komunikatów głosowych
Podgląd stanu bazy danych poprzez Panel Administracyjny
Pamięć użytkownika poprzez całą komunikację po portalu (wielo-stronnicowa)
Rozpoznanie czy użytkownik przyszedł po raz pierwszy na Portal
czy powrócił tu ponownie, np. zmieniając wyświetlaną stronę
Przekierowanie na nową stronę portalu na podstawie wydanej komendy głosowej
Głosowa wiadomość powitalna dla każdej pod-strony
Zaawansowany system zdarzeń informacji o pracy całego systemu po stronie
użytkownika, który daje wgląd w pracę całego systemu we wszystkich modułach
Kompresja wygenerowanej mowy w celu przyśpieszenia odtworzenia wiadomości
Zaimplementowana najwyższa jakość generowanego i rozpoznanego głosu, jaki daje
zastosowana biblioteka (zastosowane ustawienia na 16kHz)
Mechanizm kilkukrotnego ponowienia odtworzenia oczekującej wiadomości głosowej
o ile system nie został jeszcze w pełni zainicjalizowany
Osobne przechowywanie plików audio dla wejścia i wyjścia sygnału
41
Funkcja zapominania stanu Awatara po stronie klienta
Opcja nagrywania mowy za pomocą jednego wciśnięcia i przytrzymania przycisku
Zastosowany system znaczników mowy pozwalający na implementację tego systemu
na dowolny Portal Internetowy.
Lista przykładowych zastosowań
Odsłuchiwanie zawartości strony Portalu Internetowego
Umilenie pracy użytkownika
Nawigacja po portalu za pomocą wykorzystania własnego głosu
Zabawa z Awatarem w celu rozpoznania jego emocji poprzez odsłuchiwanie jego mowy
Dialog z Awatarem, po odpowiednim dopracowaniu Mózgu można zrobić z niego tzw.
Zwierzątko, które będzie żyło własnym życiem
Po odpowiednim dopracowaniu inteligentne wyszukiwanie i filtrowanie zawartości
portalu.
Szczegółowe opisy działania aplikacji
Aplikacja jest typu klient-server. Klient jest to część, która znajduje się po stronie użytkownika,
zapewnia ona komunikację z serwerem, jest ona interfejsem użytkownika, który wydaje
komendy głosowe, odsłuchuje komunikaty oraz przedstawia graficzną postać Awatara
zmieniającą się w zależności od swojego stanu. Serwer zajmuje się rozpoznawaniem oraz
generowaniem mowy, obsługą połączeń klientów oraz komunikacją z Mózgiem systemu.
Wykonany system jest platformą, portal demonstracyjny jest jedynie pokazem jego
zastosowania. Z tego względu działanie aplikacji możemy wydzielić na dwie części. Pierwszą
z nich jest wygenerowanie na podstawie wcześniej przygotowanych znaczników mowy
zawartości portalu, która posłuży Mózgowi do odsłuchania (wygenerowania mowy), wyszukania
informacji oraz przekierowania w odpowiednie miejsce portalu. Za tę część odpowiedzialny jest
system znaczników oraz część administracyjna, która generuje treść portalu bazując
na dostarczonym sitemap strony. Generator przechodząc kolejno przez strony zapisane
w sitemap, parsuje je, wyciąga przygotowane informacje oraz w odpowiedni sposób indeksuje
i buforuje dane w bazie danych.
Działanie samego systemu opiera się na samoistnej świadomości Awatara, który
wyposażony jest w mechanizm „zapominania”, który działa wyłącznie po stronie klienta.
Stan Awatara przechowywany jest na komputerze klienta i zapamiętywany jest nie tylko poprzez
daną sesję użytkownika (które de facto nie wymaga chociażby zalogowania się do systemu),
ale pamiętany jest od czasu ostatnich odwiedzin portalu internetowego. Pamięć ta opiera się
na mechanizmie Shared Object platformy Adobe Flash, który w uproszczeniu1 jest
odpowiednikiem pliku ciasteczka (ang. cookie). Użytkownik wchodząc na portal w pierwszej
kolejności zostaje przywitany odpowiednim komunikatem oraz zostaje zapamiętany w systemie,
tak że podczas kolejnej wizyty w trakcie danej sesji zostanie on już w inny sposób obsłużony,
tzn. otrzyma inny komunikat powitalny. Obsługa rozpoznania klienta znajduje się po stronie
Serwera Mowy, który posiada zapisaną listę aktualnie podłączonych klientów.
Podczas każdego załadowania strony następuje inicjalizacja połączenia, każde połączenie
jest weryfikowane czy może zostać obsłużone w kanale RTMP zapewniającym nagrywanie
i odsłuchiwanie komunikatów głosowych w czasie rzeczywistym. Także w tym momencie
następuje rezerwacja mikrofonu oraz zapytanie o zgodę na jego wykorzystanie. Gdy proces
ten przebiegnie pomyślnie Serwer Mowy przesyła dla klienta potwierdzenie, iż połączenie
1 Tak naprawdę Shared Object jest obiektem o wiele bardziej zaawansowanym niż ciasteczko.
42
zostało nawiązane, wówczas klient w odpowiedzi przesyła prośbę o wygenerowanie komunikatu
powitalnego oraz przesyła swój stan (emocje, które w danym momencie mogą być inne niż
domyślne). Na podstawie prośby i stanu Serwer Mowy za pośrednictwem Mózgu generuje
wiadomość powitalną, która na bazie zaktualizowanego stanu Awatara zostaje odpowiednio
zmodulowana2. Informacja o wygenerowanej i oczekującej wiadomości do odsłuchania zostaje
przesłana dla klienta, który automatycznie odtwarza ją3. Równocześnie klient otrzymuje
od Mózgu za pośrednictwem serwera mowy bodziec zmieniający stan Awatara (jego emocje),
który zostaje już stopniowo zaktualizowany po stronie klienta4.
Podobnie ma się sprawa z wysłaniem komendy głosowej, która nagrywana jest podczas
przyciśnięcia i przytrzymania przycisku „Speak”. W tym czasie mowa zostaje bezpośrednio
nagrywana na serwerze mowy. Po zakończeniu nagrywania komendy głosowej, zostaje pobrany
stan Awatara od klienta, z nagranej mowy zostaje rozpoznana komenda głosowa, która
przesyłana zostaje do Mózgu systemu. Mózg posiadając informacje o aktualnym stanie Awatara
generuje odpowiedź, która przekazywana jest do klienta. Odpowiedź zawiera wygenerowany
komunikat głosowy, zmianę stanu Awatara (emocji) oraz ewentualnie adres nowej strony,
na którą klient powinien zostać automatycznie przekierowany.
Architektura sprzętu
Elementy rozpoznawania i generowania mowy wymagają silnego serwera, aby były w stanie
obsłużyć wielu użytkowników równocześnie oraz aby zapewnić krótkie czasy reakcji
na wypowiadane i generowane komendy głosowe. Do pracy z systemem wymaga się komputera
wyposażonego w głośniki i mikrofon, które nie są wymagane po stronie serwera.
Do wykonania systemu korzystałem z dwóch środowisk:
deweloperskie – wyposażone w komputer o procesorze Pentium 4x2.66 GHz oraz 4 GB
RAM, 300 GB HDD
produkcyjne – wyposażone w komputer o procesorze Pentium 4x2.66 GHz oraz 16 GB
RAM, o przepustowości Internetu 100Mbps oraz 2TB HDD
System wymaga silnego serwera oraz dużej przestrzeni dyskowej, ponieważ dla jednego
użytkownika podczas kilku godzin pracy generowanych jest kilkaset megabajtów danych audio
w przeciągu kilku dni5.
Architektura oprogramowania
Architektura systemu została przedstawiona na rys. 7.1. Cały system został wytworzony
w technologii Java/Flex. Technologia Adobe Flex została wykorzystana do utworzenia klienta,
natomiast część serwerowa została wykonana w technologii Java. Budowa platformy
jest modułowa, umożliwiająca niezależny rozwój i wymianę każdego z modułów.
2 Modulacja polega na zmianie parametrów audio generowanej mowy.
3 Odtworzenie nagranego komunikatu głosowego następuje w czasie rzeczywistym przy wykorzystaniu protokołu
RTMP. 4 Zmiany stanu (emocji) Awatara dokonywane są po stronie klienta na bazie otrzymanych informacji o ile dany stan
(emocja) powinien zostać zmieniony. 5 Do klienta zostaje przesłany materiał w wersji skompresowanej o kilku, a czasem kilkunasto krotnie mniejszej
objętości. Tylko po stronie serwera zostają wygenerowane dane o dostępnej najwyższej jakości danych, które
następnie zostają skompresowane i przekazane dla klienta.
43
Rys. 7.1. Ogólna architektura systemu.
Część kliencka odpowiada za komunikację z serwerem, przydzieleniem uprawnień do mikrofonu
i głośników, wygenerowaniem unikalnego identyfikatora dla klienta, który następnie jest
wykorzystywany przez serwer do jego identyfikacji. Także w tej części użytkownik ma kontakt
z systemem, obserwuje wizualnie postać Awatara, wydaje komendy oraz słucha systemu, tutaj
także znajduje się aktualna wizualizacja stanu Awatara w postaci wykresu słupkowego oraz
konsola logów z całego systemu, w której użytkownik monitoruje stan systemu w danym
momencie. Po stronie klienta przechowywany jest aktualny stan Awatara unikalny dla każdego
użytkownika. jednak jest on przypisany na stałe do danego komputera, a czasem przeglądarki.
Część serwerowa została oznaczona kolorem zielonym. Fizycznie część ta podzielona
jest na dwa serwery: serwer mowy jest serwerem czasu rzeczywistego, jest to serwer Red5 RC
1.0, który opiera się na platformie Apache Tomcat 6 działający na porcie 8080 dla protokołu
HTML oraz na porcie 1935 dla protokołu RTMP, drugim serwerem jest Oracle WebLogic 11g
na którym działa „Mózg” systemu, część administracyjna oraz portal demonstracyjny. Sewer
standardowo działa na porcie 7001 dla protokołu HTML.
Serwer mowy jest kluczowym elementem całego systemu, zwyczajowo nazywany
„serwerem” niniejszego systemu. To bezpośrednio z nim komunikuje się klienta (a w zasadzie
użytkownik systemu). Serwer ten wydziela dalszą interakcję z użytkownikiem. Na serwerze tym
zostały utworzone moduły generowania i syntezy mowy, klienta mózgu, obsługi i konwersji
danych audio przesyłanych w czasie rzeczywistym. Także w tym miejscu znajduje się lista słów
i komend rozpoznawalnych przez system w formacie JSGF (ang. Java Speech Grammar
Format). W tym miejscu mamy konfiguracje systemów rozpoznawania i syntezy mowy
w formatach zdefiniowanych przez specyfikacje bibliotek FreeTTS oraz Sphinx4.
44
Wykonany serwer mowy posiada architekturę wielo-wątkową6 (ang. Multithreading),
tak więc każde żądanie nowego klienta obsługiwane jest w nowym wątku. Konwersje formatów
danych audio są także wykonywane wielo wątkowo. Rozpoznanie komendy głosowej
z wygenerowaniem odpowiedzi również zachodzi w osobnym wątku. Wymienione elementy
posiadają wykonaną własną implementację nie zależną od standardu Javy, która samodzielnie
próbuje dzielić zadania na wiele wątków.
Mózg jest drugim serwerem, który odpowiada za logikę aplikacji. Komunikuje się
on bezpośrednio i wyłącznie z systemem mowy, od którego dostaje komendę użytkownika już
w postaci tekstowej, identyfikator klienta oraz stan Awatara oraz zwraca akcję do wykonania
przez klienta wraz z komendą zmiany stanu Awatara lub wykonania przekierowania do innej
strony w portalu internetowym. Komunikacja systemu mowy następuje poprzez klienta Mózgu,
który komunikuje serwer mowy z „Mózgiem” poprzez protokół SOAP przy wykorzystaniu
dokumentu WSDL w technologii rozproszonej SOA. To rozwiązanie umożliwia łatwe
dodawanie nowych implementacji Mózgu oraz przełączanie ich w czasie działania aplikacji.
Mózg na podstawie otrzymanych danych i przetworzeniu posiadanych decyduje o wykonanej
interakcji w portalu internetowym za pośrednictwem serwera mowy, który dodatkowo może
wygenerować komunikaty głosowe dla użytkownika oraz przesłać komunikaty zmiany stanu
Awatara lub zadecydować o wykonaniu przekierowania użytkownika do innej strony na portalu
internetowym.
Klient łączy się z częścią serwerową za pośrednictwem protokołów RTMP i AMF (ang. Action
Message Format). Za pomocą protokołu RTMP przesyłany jest wyłącznie sygnał audio w czasie
rzeczywistym bez potrzeby przesyłania całego materiału w całości7. Natomiast protokół AMF
służy wyłącznie do wymiany komunikatów pomiędzy serwerem a klientem. Komunikaty
te zawierają informacje typu: stan Awatara, komendy tekstowe przesyłane do serwera w postaci
czytelnego tekstu, informacje zwrotne dla klienta kiedy czeka na niego wiadomość
do odsłuchania, komunikaty logów8 ze strony serwera itp. Klient-serwer komunikuje się ze sobą
wyłącznie za pośrednictwem tych dwóch protokołów.
Serwer mowy komunikuje się z Mózgiem za pośrednictwem protokołu SOAP
w technologii rozproszonej SOA. Wyłącznie Mózg komunikuje się z bazą danych. W ten sposób
zostały jasno wyznaczone warstwy systemu.
Opis metody wytwarzania aplikacji
Cykl wytworzenia niniejszej aplikacji przedstawia się następująco:
1. zapoznanie się z technologią Speech oraz AI
2. wykonanie systemu analizy serwisu
3. system generowania oraz rozpoznawania mowy
4. silnik AI wraz z inicjacją "świadomości" bota oraz z implementacją 4 stanów emocji
5. stworzenie prostej graficznej postaci Awatara komunikującej się z silnikiem AI
6 Architektura wielo-wątkowa pozwala na równoległą realizację żądań wielu użytkowników równocześnie.
Najczęściej żądania są rozdzielane równomiernie na dostępne procesory i rdzenie dzięki czemu wykonywane
operacje powinny odbywać się szybciej (choć nie zawsze, jeżeli liczba wątków jest zbyt duża) i powinny pozwolić
na wykonanie danej operacji przez nowego użytkownika bez potrzeby oczekiwania na zakończenie wykonywanych
operacji przez innych użytkowników (jednak w praktyce nie zawsze tak jest, jest to zależne od danej
implementacji). 7 Protokół RTMP w odróżnieniu od innych protokołów naprawdę przesyła dane w czasie rzeczywistym. Inne
protokoły najczęściej „udają”, że przesyłają materiał czasie rzeczywistym, lecz np. przesyłają go w partiach, a nie
bit po bicie jak dzieje się to w przypadku wykorzystania protokołu RTMP. 8 Komunikaty te informują klienta co dzieje się po stronie serwera. W przypadku jakichkolwiek problemów
po stronie serwera klient jest informowany o tym fakcie.
45
6. stworzenie demonstracyjnego serwisu
7. przygotowanie odpowiedniej dokumentacji.
Założenia i sformułowane zadania
Celem niniejszego projektu jest wytworzenie systemu, który docelowo będzie można wdrożyć
w istniejący portal PG. Z tego powodu zostało założone wykonanie mechanizmu
dostosowującego się do istniejącego portalu internetowego, który powinien działać
po wykonaniu minimalnego nakładu pracy.
Kolejne założenia dotyczą samego systemu, który powinien działać w Internecie oraz nie
powinien wymagać zalogowania się. Do komunikacji z użytkownikiem powinna zostać
wykorzystana postać Awatara wyposażona w swój stan (emocje), dzięki którym będzie się
inaczej zachowywała9 oraz będzie generowała mowę w inny sposób
10.
System ma składać się z niezależnych modułów umożliwiających łatwą jego rozbudowę.
Mózg systemu podejmujący decyzję i generujący odpowiedź11
powinien być niezależny
od implementacji poprzez wykorzystanie technologie usług internetowych (ang. Web Service),
który umożliwi jego nieograniczony rozwój bez wymuszenia zastosowania konieczności
zastosowania konkretnego języka programowania.
Specyfikacje
System operacyjny:
Linux
Windows
Macintosh.
Serwer aplikacji:
Oracle WebLogic
Red5.
Języki programowania:
JavaEE
ActionScript.
Baza danych:
Oracle.
Główne bibloteki:
Rozpoznawanie mowy: Sphinx-4
Generowanie mowy: FreeTTS.
Przygotowanie projektu
Projekt został udostępniony i zainstalowany na serwerze dedykowanym, który dzierżawię.
W pełni funkcjonalny jest on dostępny pod adresem internetowym:
9 Zachowanie postaci Awatara zostało odwzorowane w animacji.
10 Poprzez wykorzystanie modulacji wygenerowanego głosu.
11 Podjęcie decyzji ma za zadanie wygenerowanie odpowiedniej odpowiedzi w zależności od kontekstu, w którym
w danej chwili znajduje się postać Awatara.
46
http://94.23.252.15:7001/portal/index.xhtml
Prototypowanie i implementacja
Ze względu na charakter niniejszego projektu, który jest pewnym novum na rynku można
powiedzieć, że większość prac z projektem była pewnym etapem prototypowania. Z każdym
miesiącem projekt ewoluował, rozwijał się w miarę dokonanych badań, testów i uzyskanych
wyników, tak aby docelowo uzyskał on najlepszy możliwy wynik. Implementacja była
już wynikiem wcześniej wykonanych prac.
Testowanie
Podstawową techniką testowania systemu jest wykorzystanie jednostek testowych, które
weryfikują cząstkowe działanie systemu. System powstawał krok po kroku, z małego rozwinął
się w duży wykonując testy użytkownika podczas jego działania. Skala złożoności i zależności
systemu jest tak duża, że do pomocy został wykonany specjalny system logowania, który
informuje użytkownika12
o pracy całego systemu umożliwiając wykrycie różnego rodzaju
dolegliwości i problemów w systemie, którego przetestowanie jest o tyle skomplikowane,
iż opiera się na mowie, stanie Awatara, który nie bazuje na sesji a dodatkowo rozproszony jest
na wiele pod stron, niezależnych żądań przekierowań, ale jednak tworzących pewną całość,
która była testowana poprzez użytkownika systemu.
Ocena aplikacji oraz porównanie do innych rozwiązań
W chwili obecnej nie udało mi się znaleźć żadnego odpowiednika niniejszego systemu13
lub systemu, który w dużej części odpowiadał by obecnej realizacji. Do jego realizacji
poświęciłem naprawdę wiele czasu, wiele nocy nie przespałem, kilka tygodni wolnego z pracy
także zostało spożytkowane w tym celu. Sporą część realizacji zawdzięczam Panu prof. dr hab.
inż. Zdzisław Kowalczuk, który nadał kierunek i pokierował pracą w tę stronę, w której praca
obecnie się znajduje a także poświęcił wiele czasu nad jej realizacją, za co jestem bardzo
wdzięczny. Te wszystkie elementy potwierdzają wyłącznie iż praca została wykonana żetelnie,
z największą starannością, ale także zawiera wiele unikalnych i nowatorskich rozwiązań, która
zapracowała i zasługuje na najwyższą ocenę.
Wnioski i perspektywy dalszych prac
Wykonanie systemu było dla mnie niezwykle rozwijające, nauczyłem się wiele z samych
technologii informatycznych, ale także poznałem elementy, które nadają „życia” temu
komputerowemu projektowi. Był to czas, którego nie żałuję iż wykorzystałem go w taki a nie
w inny czas.
Projekt obecnie znajduje się na wstępnej rozwojowej drodze, tak więc nadaje się
on do podjęcia jego rozbudowy i dopracowania, tak aby był w stanie godnym przedstawienia
go szerszemu gronu odbiorców.
12
Operator systemu. 13
Mogę z wielką pewnością stwierdzić, iż taki odpowiednik nie istnieje wcale.
47
9.Dodatek 2. Instrukcja dla użytkownika
Instrukcja opisuje sposób sterowania Portalem Internetowym za pomocą głosu.
Adres internetowy portalu demonstracyjnego:
http://94.23.252.15:7001/portal/index.xhtml
Przygotowanie do pracy z systemem
Wymagania do rozpoczęcia pracy z systemem:
połączenie do Internetu
przeglądarka internetowa z wtyczką Adobe Flash Player 10 lub nowszym
głośniki i mikrofon.
Zaleca się skorzystanie ze słuchawek z mikrofonem w celu większego odseparowania
użytkownika od otoczenia.
Do pełnej wygody podczas pracy z aplikacją zaleca się skonfigurowanie zabezpieczeń
systemu Adobe, aby za każdym razem system nie pytał się nas o wyrażenie zgody
na wykorzystanie mikrofonu.
Wydanie zgody dla aplikacji na korzystanie z mikrofonu
Polityka bezpieczeństwa Adobe wymusza na nas każdorazowo o wydanie zgody
na wykorzystanie mikrofonu. Ma to na celu np. uniemożliwienie podsłuchiwania użytkowników
poprzez Internet, dlatego więc użytkownik zmuszony został do świadomego wyrażenia zgody
na podłączenie mikrofonu do aplikacji jak pokazano na poniższym rysunku:
Rys. 8.1. Prośba o wyrażenie zgody na użycie mikrofonu.
Istnieje jednak możliwość, aby globalnie taką zgodę (wówczas aplikacja nie będzie
już wyświetlała okienka jak powyżej i domyślnie pozwoli na wykorzystanie mikrofonu). Zostało
to opisane poniżej. Opis dotyczy najnowszej wersji wtyczki Adobe Flash 11 w wersji polskiej.
Klikamy prawym przyciskiem myszki na postać Awatara i wybieramy opcję „Ustawienia
globalne…”:
48
Rys. 8.2. Wybranie opcji „Ustawienia globalne…”.
W „Menedżer ustawień programu Flash Player” przechodzimy do zakładki „Kamera
i mikrofon” oraz wciskamy przycisk „Ustawienia kamery i mikrofonu wg witryn…”:
Rys. 8.3. Przejście do ustawień kamery i mikrofonu.
W oknie „Ustawienia kamery i mikrofonu wg witryn” zaznaczamy w głównym oknie
witrynę internetową o nazwie „94.23.252.15” poprzez kliknięcie jej (wówczas zostanie
ona podświetlona na niebiesko):
49
Rys. 8.4. Wybranie adresu portalu demonstracyjnego.
Następnie w części „Gdy zaznaczona witryna spróbuje użyć kamery lub mikrofonu:”
wybieramy opcję „Zezwól” (domyślnie klikamy na listę o nazwie „Pytaj mnie
o pozwolenie” i zmieniamy ją na „Zezwól”):
50
Rys. 8.5. Zezwolenie na wykorzystanie mikrofonu dla portalu demonstracyjnego.
Końcowe ustawienia powinny wyglądać jak na rysunku poniżej. Na zakończenie klikamy
„Zamknij”. Od tego momentu aplikacja nie powinna się już nas pytać o możliwość
skorzystania z mikrofonu.
Rys. 8.6. Widok ostatecznej konfiguracji reguł systemu zabezpieczeń Adobe.
Komendy sterujące pracą systemu
Sterowanie portalem odbywa się poprzez wydawanie komend głosowych oraz poprzez
udzielanie odpowiedzi na wydane zapytania ze strony systemu. Wszystkie komendy należy
wydawać głośno i wyraźnie. Zaleca się użycie słuchawek z mikrofonem. Należy mieć
na uwadze, aby w pomieszczeniu, w którym odbywa się komunikacja z portalem panowała
cisza. Wszelkie hałasy, dźwięki w tle mogą utrudnić lub uniemożliwić poprawne rozpoznawanie
komend głosowych poprzez system, co w konsekwencji może uniemożliwić poprawne
rozpoznawanie komend głosowych poprzez system.
W celu wydania komendy głosowej należy wcisnąć przycisk „Speech”, wydać komendę
głosową do mikrofonu oraz puścić przycisk.
Podstawowe komendy użytkownika zostały przedstawione w tab. 8.1.
Tab. 8.1. Komendy użytkownika.
l.p. komenda działanie
1. „Good Morning” awatar powinien przywitać się z nami
2. „Hello” awatar wita się
3. „How are You ?” awatar odpowiada nam jak się czuje i pyta się nas
o to samo
4. „What is Your Name ?” awatar odpowiada nam jak się nazywa
51
5. „Read page” awatar czyta zawartość głównej strony
6. „Home”, itp. następuje przeniesienie do odpowiedniej strony
7. „Next page” powodują przejście do następnej strony
8. „Previous page” powodują przejście do poprzedniej strony
Lokalne ustawienia systemu
Użytkownik może zdecydować czy chce wyświetlać emocje awatara lub logi z systemu.
Dokonuje tego poprzez diwie opcje:
Show info – decyduje czy system ma wyświetlać emocje Awatara,
Show log – określa czy wyświetlać logi z systemu.
Opcje te po przełączeniu są od razu zapisywane i nie trzeba wciskać przycisku „Save”1. Panel
ustawień został zaprezentowany na rys. 8.7. Część opcji została domyślnie wyłączona przed
modyfikacją użytkownika2.
Adres: http://94.23.252.15:7001/portal/avatar/settings.xhtml
Rys. 8.7. Ustawienia aplikacji klienta.
1 Co więcej opcje te są zapisywane i pamiętane nawet po zamknięciu danej przeglądarki internetowej.
2 Jest to celowe uproszczenie systemu. Opcje te można odkomentować w kodzie źródłowym dołączonej aplikacji.
52
10.Dodatek 3. Instrukcja dla administratora
Administrator systemu odpowiada za przygotowanie portalu internetowego do pracy
z systemem, wygenerowanie treści portalu oraz zdefiniowanie komunikatów głosowych
wypowiadanych przez Awatara. Do tego celu został przygotowany system znaczników oraz
Panel Administracyjny. W celu wybrania poszczególnych stron portalu do obsługi w systemie
wykorzystuje się mapę strony (ang. sitemap).
System znaczników
W celu oznaczenia treści portalu, które mają zostać wykorzystane w systemie został
zdefiniowany specjalny system znaczników, którymi obejmuje się fragmenty strony
internetowej1.
Podstawowa budowa znacznika z wymaganymi elementami:
<speech value=" " weight"”>TREŚĆ</speech>
TREŚĆ – treść, która może być uwzględniona podczas generowania mowy.
Tab. 9.1. Atrybut „value”.
l.p. wartość opis
1. MENU element menu
2. TITLE tytuł strony lub głównego elementu na stronie
3. INTRO skrócona zawartość strony. Opis zawartości strony
4. TEXT główna treść strony
5. LINK link w części strony
6. PEOPLE wykładowca
7. SUBJECT przedmiot dydaktyczny
8. SUBJECT_DATA dodatkowe informacje na temat przedmiotu
Atrybut „value” oznacza typ zaindeksowanej zawartości. Atrybut ten został opisany w tab. 9.1.
Tab. 9.2. Atrybut weight.
l.p. wartość opis
1. 0 element najważniejszy
2. 1 element średnio ważny
3. 2 element najmniej ważny
Atrybut „weight” oznacza ważność danego elementu. Za jego pomocą można określić
np. iż menu główne jest bardziej istotne z punktu widzenia systemu niż menu poboczne2.
Atrybut ten został opisany w tab. 9.2.
1 Znacznikami mowy można objąć zarówno statyczne jaki i dynamiczne fragmenty stlony internetowej.
2 Takie rozumowanie wydaje się logiczne, jednak system sam nie myśli, dlatego taka logika została jawnie
zdefiniowana.
53
W systemie istnieją także inne znaczniki, które zawierają więcej atrybutów. Należą do nich
znaczniki typu3:
PEOPLE,
SUBJECT,
SUBJECT_DATA.
Znacznik typu PEOPLE:
<speech value="PEOPLE" weight="" degree="" function="" moreUrl="">IMIĘ
I NAZWISKO</speech>
Tab. 9.3. Dodatkowe atrybuty znacznika typu PEOPLE.
l.p. atrybut wartość
1. degree stopień naukowy.
2. function zajmowane stanowisko,
3. moreUrl adres strony internetowej z informacją na temat
wykładowcy.
Znacznik typu SUBJECT:
<speech value="SUBJECT" type="">NAZWA PRZEDMIOTU</speech>
Tab. 9.3. Dodatkowe atrybuty znacznika typu SUBJECT.
l.p. atrybut wartość
1. type rodzaj przedmiotu.
Znacznik typu SUBJECT_DATA:
<speech value="SUBJECT_DATA" name="" subjectForm="" noOfHours="" teachers=""
taughtIn="" studyLevel="" semester=”" department=""/>
Tab. 9.4. Dodatkowe atrybuty znacznika typu SUBJECT_DATA.
l.p. atrybut wartość
1. name nazwa przedmiotu. Powinna być taka jak podana w
znaczniku typu SUBJECT.
2. subjectForm rodzaj zajęć.
3. noOfHours liczba godzin w semestrze.
4. taughtIn kierunek.
5. studyLevel poziom studiów.
6. semester semestr.
7. department wydział.
3 Typ znacznika został zdefiniowany poprzez atrybut „value”.
54
Mapa strony – sitemap
Na potrzeby systemu została wykorzystana standardowa mapa strony (ang. sitemap).
Za jej pomocą definiuje się listę stron do zaindeksowania dla systemu.
Przykładowa mapa strony:
http://94.23.252.15:7001/portal/index.xhtml
http://94.23.252.15:7001/portal/people.xhtml
http://94.23.252.15:7001/portal/people/zdzislaw_kowalczuk.xhtml
http://94.23.252.15:7001/portal/specialization.xhtml
http://94.23.252.15:7001/portal/didactics/process_diagnostics.xhtml
http://94.23.252.15:7001/portal/teaching.xhtml
http://94.23.252.15:7001/portal/research.xhtml
http://94.23.252.15:7001/portal/laboratories.xhtml
http://94.23.252.15:7001/portal/smart_control_idea.xhtml
Panel administracyjny
System został wyposażony w panel administracyjny ułatwiający pracę wygenerowania oraz
zweryfikowania zawartości platformy a także zmiany generowanych komunikatów głosowych.
Adres panelu administracyjnego:
http://94.23.252.15:7001/portal/admin/index.xhtml
Dane logowania:
Login: admin
Hasło: etiavatar1
Edycja generowanych odpowiedzi z systemu – Avatar Speech
Adres: http://94.23.252.15:7001/portal/admin/index.xhtml
W celu zmiany generowanych odpowiedzi przez system należy skorzystać z tego panelu.
Wybieramy komunikat głosowy, klikamy po prawej stronie przycisk „edit”, zmieniamy jego
treść oraz klikamy przycisk „save”4 w celu zapisania zmian. Panel ten został przedstawiony
na rys. 9.1. Tabela 9.4 przedstawia domyślne treści komunikatów głosowych.
Tab. 9.4. Dodatkowe atrybuty znacznika typu SUBJECT_DATA.
l.p. Typ komunikatu Domyślny komunikat
1. WELCOME_BACK Welcome back my dear
2. DONT_UNDERSTAND I don't understand You. Please repeat.
3. PLEASE_REPEAT Please repeat.
4. YOU_SAID I thing that I don't understand you well. Have you said
5. HELLO Hello. It's nice to meet you.
6. MY_NAME_IS My name is Avatar. What is your name ?
7. GOTO_PAGE I'am moving you to the page
4 W trybie edycji przycisk „save” pojawia się w miejscu przycisku „edit”.
55
8. PAGE_ABOUT About Department! The Department of Decision Systems
was created in 2006 by professor Zdzislaw Kowalczuk
and, of interests of the Department concern modeling and
identification, diagnostics, control and decision systems.
Scientific research involve planning and control both
industrial and economical processes as well as developing
modern design methods using computer techniques and
tools. The methods derived result both from classical
analysis and artificial/computational intelligence.
Industrial process diagnosis, methods of system and
signal analysis and filtering, estimation of dynamical
plants state, production and path planning and other
problems, which enhance the characterization of the
Department, are utilized in automatic control systems,
mobile and flying robots, automotive vehicles and other
industrial objects. In the field of teaching the
Department’s staff together with the staff of the
Automatic Control Department play the role of a
principal leader in the BSc/MSc course of Automatic
Control and Robotics in the Faculty of Electronics
Telecommunication and Informatics of the Gdansk
University of Technology.
9. WELCOME_ON_PORTAL Welcome on avatar speech portal. This is an engineer
thesis work.
10. WELCOME Good morning my dear. What's a beautyfull day. How are
you?
56
Rys. 9.1. Edycja generowanych odpowiedzi z systemu.
Generator treści systemu
Generator tworzy bazę danych na bazie uprzednio przygotowanych znaczników mowy
na podstawie listy stron zapisanych w mapie strony (ang. sitemap).
Adres: http://94.23.252.15:7001/portal/admin/sitemap.xhtml
Rysunek 9.2 przedstawia panel. Zawiera on wyłącznie dwie opcje:
Show – która wyświetli zawartość sitemap,
Generate – generuje zawartość portalu5.
W polu „Sitemap” należy podać lokalizację pliku sitemap.
5 Uwaga: opcja ta wyczyści aktualnie przechowywane informacje na portalu, jednak nie zmodyfikuje treści
generowanych komunikatów mowy przez system. Niepoprawne skonfigurowanie uniemożliwi dalszą pracę
z systemem do czasu przygotowania poprawnych danych.
57
Rys. 9.2. Generator treści systemu.
Podgląd zawartości wygenerowanej bazy danych na bazie znaczników
W każdym momencie możemy podejrzeć zawartość wygenerowanej bazy danych na bazie
zastosowanych znaczników mowy.
Adres:
http://94.23.252.15:7001/portal/admin/pages.xhtml
http://94.23.252.15:7001/portal/admin/speech.xhtml
http://94.23.252.15:7001/portal/admin/peoples.xhtml
http://94.23.252.15:7001/portal/admin/subjects.xhtml
Instrukcja wdrożenia systemu
Na dołączonej płycie DVD znajduje się przygotowana paczka systemu w wersji do wdrożenia
na serwer. Paczka ta kompatybilna jest ze standardem JavaEE6 EAR. Zawiera ona przykładową
konfigurację pod serwer Oracle WebLogic. Instalacja odbywa się w sposób standardowy
dla tego typu serwerów tak więc można ją wykonać według instrukcji dołączonej do serwera.
Dodatkowo poza paczką EAR na płycie znajduje się aplikacja SpeechServer, którą należy
wdrożyć na serwer RTMP Red5. Paczka ta jest już skonfigurowana do pracy pod tym serwerem,
wdraża się ją jak na serwer Apache Tomcat. Schemat wdrożeniowy został przedstawiony
na rys. 9.3. Przed instalacją systemu na serwery należy wpierw utworzyć bazę danych ze
skryptów dołączonych na płycie oraz dodać DataSource na serwerze JavaEE o nazwie
SPEECHDS z połączeniem do nowo utworzonej bazy danych. Serwer mowy wymaga
zainstalowanej biblioteki Xuggler.
6 Dodatkowo znajduje się w niej plik wdrożeniowy na serwer Oracle WebLogic. Istnieje jednak możliwość,
aby tak przygotowaną paczkę wdrożyć na inny serwer JavaEE po odpowiednim przygotowaniu.
58
Główne pliki konfiguracyjne:
SpeechServer\src\com\janklimczak\speech\server\Settings.java – zawiera adres folderu
na serwerze do przechowywania plików audio
SpeechAvatar\src\com\janklimczak\speech\communication\ResponseToServer.as – adres
serwera RTMP wraz z nazwą aplikacji SpeechServer7
Pozostałe pliki konfiguracyjne wynikają ze specyfikacji JavaEE, tak więc na ich temat można
znaleźć więcej w dokumentacji Java.
Rys. 9.3. Diagram wdrożenia systemu.
7 System zawiera implementację tego adresu jako opcja konfiguracyjna z panelu administracyjnego. Opcja
ta ze względu na uproszczenie została zakomentowana i wpisana w tym miejscu na sztywno.
59
11.Dodatek 4. Instrukcja dla programisty
Instrukcja zawiera wskazówki dla webmasterów i programistów rozwijających system. System
posiada udokumentowany kod źródłowy aplikacji w postaci komentarzy wykonanych w języku
angielskim. Dodatkowo do systemu została dołączona dokumentacja techniczna zawierająca opis
funkcji (do każdego modułu), która znajduje się na dołączonej płycie DVD (rys. 10.1).
Rys. 10.1. Dołączona dokumentacja projektu javadoc.
Budowa bazy danych
Rys. 10.2. Budowa bazy danych.
60
Specyfikacja komunikacji z Mózgiem
Rys. 10.3. Komunikacja z Mózgiem poprzez WSDL.
61
Klient (ActionScript) – diagram klas
Rys. 10.4. Diagram klas modułu klienta.
62
Serwer Mowy (JavaEE)– diagram klas
Rys. 10.5. Diagram klas Serwera Mowy.
63
System znaczników.
Aby system mógł poprawnie funkcjonować należy oznaczyć treści portalu, które mają
być uwzględnione w systemie mowy. Realizacja polega na dodaniu znaczników Speech do treści
dokumentów webowych1. Do tego celu zostały przygotowane znaczniki w postaci:
<speech value="SpeechType" weight="SpeechWeight"> treść </speech>
Wartości SpeechType oraz SpeechWeight należy zastąpić tymi z rys. 10.6. opisanymi
w tab. 10.1. i 10.2.
Rys. 10.6. Wartości atrybutów znacznika speech.
Tab. 10.1. Wartości atrybutu SpeechType.
l.p. atrybut opis
1. MENU pozycja z menu
2. TITLE tytuł strony
3. INTRO streszczenie zawartości strony
4. TEXT zawartość strony
5. LINK link do innej strony
6. PEOPLE wykładowca
7. SUBJECT przedmiot/specjalizacja
8. SUBJECT_DATA opis przedmiotu/specjalizacji
Tab. 10.2. Wartości atrybutu SpeechWeight.
l.p. atrybut opis
1. FIRST = 0 element najbardziej istotny
2. SECOND = 1 element umiarkowany
3. THIRD = 2 element najmniej istotny
Dla znaczników typu PEOPLE wymagane są dodatkowe atrybuty zdefiniowane w tab. 10.3.
1 Znaczniki te w czytelnej postaci powinny znaleźć się w wygenerowanych plikach HTML.
64
Tab. 10.3. Wartości dodatkowych wymaganych atrybutów dla znacznika typu PEOPLE.
l.p. atrybut opis
1. NAME imię i nazwisko
2. DEGREE stopień naukowy
3. FUNCTION obejmowana funkcja
4. URL adres strony WWW zawierającej informacje o
osobie
Dla znaczników typu SUBJECT wymagane są dodatkowe atrybuty zdefiniowane w tab. 10.4.
Tab. 10.4. Wartości dodatkowych wymaganych atrybutów dla znacznika typu SUBJECT.
l.p. atrybut opis
1. NAME nazwa przedmiotu/specjalizacji
2. TYPE przeznaczenie dla specjalizacji
Dla znaczników typu SUBJECT_DATA wymagane są dodatkowe atrybuty zdefiniowane
w tab. 10.5.
Tab. 10.5. Wartości dodatkowych wymaganych atrybutów dla znacznika typu
SUBJECT_DATA.
l.p. atrybut opis
1. NAME nazwa przedmiotu/specjalizacji
2. SUBJECTFORM rodzaj zajęć, np. laboratorium
3. NOOFHOURS liczba godzin w semestrze
4. TEACHERS lista nauczycieli oddzielona znakiem #
5. TAUGHTIN specjalizacja
6. STUDYLEVEL poziom studiów
7. SEMESTER semestr
8. DEPARTMENT wydział
Przykładowy znacznik dla elementu menu pierwszego poziomu:
<speech value="MENU" weight="O">Home Page </speech>
Dodanie postaci Awatara
W celu umożliwienia wydawania i odsłuchiwania komend dla systemu należy dodać postać
Awatara i klienta do portalu internetowego, aby była ona widoczna na wszystkich stronach
w których będziemy korzystali z systemu sterowania głosem. Postać Awatara została wykonana
w technologii Flash, aby ją dodać do strony należy wkleić poniższy fragment kodu na stronę
portalu: <script language="JavaScript" type="text/javascript"> <!-- // Version check for the Flash Player that has the ability to start Player Product Install (6.0r65) var hasProductInstall = DetectFlashVer(6, 0, 65);
65
// Version check based upon the values defined in globals var hasRequestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion, requiredRevision); if ( hasProductInstall && !hasRequestedVersion ) { // DO NOT MODIFY THE FOLLOWING FOUR LINES // Location visited after installation is complete if installation is required var MMPlayerType = (isIE == true) ? "ActiveX" : "PlugIn"; var MMredirectURL = window.location; //document.title = document.title.slice(0, 47) + " - Flash Player Installation"; var MMdoctitle = document.title; AC_FL_RunContent( "src", "playerProductInstall", "FlashVars", "MMredirectURL="+MMredirectURL+'&MMplayerType='+MMPlayerType+'&MMdoctitle='+MMdoctitle+"", "width", "100%", "height", "100%", "align", "middle", "id", "SpeechAvatar", "quality", "high", "bgcolor", "#ffffff", "name", "SpeechAvatar", "allowScriptAccess","sameDomain", "type", "application/x-shockwave-flash", "pluginspage", "http://www.adobe.com/go/getflashplayer" ); } else if (hasRequestedVersion) { // if we've detected an acceptable version // embed the Flash Content SWF when all tests are passed /*AC_FL_RunContent( "src", "#{request.contextPath}/avatar/SpeechAvatar.swf", "flashVars", "client_id=#{session.id}", "width", "215", "height", "138", "align", "middle", "id", "SpeechAvatar", "quality", "high", "bgcolor", "#ffffff", "name", "SpeechAvatar", "allowScriptAccess","sameDomain", "type", "application/x-shockwave-flash", "pluginspage", "http://www.adobe.com/go/getflashplayer" );*/ } else { // flash is too old or we can't detect the plugin var alternateContent = '' + 'This content requires the Adobe Flash Player. ' + '<a href=http://www.adobe.com/go/getflash/>Get Flash</a>'; document.write(alternateContent); // insert non-flash content } // --> </script> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="SpeechAvatar" width="215" height="138" codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab"> <param name="movie" value="#{request.contextPath}/avatar/SpeechAvatar.swf" /> <param name='flashVars' value='client_id=#{session.id}'/> <param name="quality" value="high" /> <param name="bgcolor" value="#ffffff" />
66
<param name="allowScriptAccess" value="sameDomain" /> <embed src="#{request.contextPath}/avatar/SpeechAvatar.swf" quality="high" bgcolor="#ffffff" width="215" height="138" name="SpeechAvatar.swf" align="middle" flashVars="client_id=#{session.id}" play="true" loop="false" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer"> </embed> </object>
Należy w kodzie zamienić fragment „#{session.id}” na inny generator unikalnego identyfikatora
udostępnionego przez portal.
Konfiguracja systemu rozpoznawania i generowania mowy
Moduły odpowiedzialne za konfigurację rozpoznawania i generowania mowy znajdują się
na Serwerze Mowy. Dokumentacja ich konfiguracji znajduje się na stronach ich producentów
(FreeTTS i Sphinx-4).
Pliki konfiguracyjne:
SpeechServer\src\speech.properties
SpeechServer\src\com\janklimczak\speech\server\recognize\config.xml
SpeechServer\src\com\janklimczak\speech\server\recognize\hello.gram
SpeechServer\src\com\janklimczak\speech\server\recognize\SpeechRecognize.java
SpeechServer\src\com\janklimczak\speech\server\syntetize\SpeechSyntetizer.java
67
12.Dodatek 5. Listing kodów źródłowych głównych modułów
Dodatek ten zawiera listing głównych kodów źródłowych. Opis działania znajduje się
bezpośrednio przy kodzie w postaci komentarzy w języku angielskim w formacie Javadoc i
ASDoc w zależności odwykorzystanej technologi (Java lub ActionScript). Dodatkowo na płycie
została dołączona pełna wygenerowana dokumentacja w tych powyższych formatach.
Serwer mowy - SpeechServer
SpeechServer\src\com\janklimczak\speech\server\Engine.java:
package com.janklimczak.speech.server;
import com.janklimczak.speech.brain.client.model.Question;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IStreamAwareScopeHandler;
import org.red5.server.api.stream.ISubscriberStream;
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import com.janklimczak.speech.server.audio.ConvertAudio;
import com.janklimczak.speech.server.client.ClientAttribute;
import com.janklimczak.speech.server.client.ClientContainer;
import com.janklimczak.speech.server.speech.ResponseToFlex;
import com.janklimczak.speech.server.speech.Speech;
import com.janklimczak.speech.server.utils.SpeechLogger;
import com.xuggle.mediatool.IMediaReader;
import com.xuggle.mediatool.ToolFactory;
/**
* Speech Server.
*
* @author Jan Klimczak
*/
public class Engine extends MultiThreadedApplicationAdapter implements IStreamAwareScopeHandler {
/** Container for connected clients */
final private Map<String, ClientContainer> connections = new ConcurrentHashMap<String,
ClientContainer>();
/** Logger */
private static SpeechLogger logger = new SpeechLogger(Engine.class);
/** Straem counter */
private int streamCounter = 0;
/** Speech system */
private Speech speech = new Speech();
68
/**
* Start and initialize Speech Server.
*
* @param app Scope
* @return true if succes
*/
@Override
public boolean appStart(IScope app) {
logger.info("SERVER RTMP ZOSTAL URUCHOMIONY");
this.speech = new Speech();
return super.appStart(app);
}
/**
* Connect client into server.
*
* {@inheritDoc}
* @param conn IConnection
* @param scope IScope
* @param params Client params with client id
* @return true if succes
*/
@Override
public boolean connect(IConnection conn, IScope scope, Object[] params) {
String clientId = params[0].toString();
IClient client = conn.getClient();
client.setAttribute(ClientAttribute.CONNECTION_ID.toString(), clientId);
ClientContainer cContainer;
if (connections.containsKey(clientId)) {
cContainer = connections.get(clientId);
IConnection realConnection = cContainer.getConnection();
if (realConnection.isConnected()) {
// Client is already connected
IClient realClient = realConnection.getClient();
String realClientId = realClient.getId();
client.setId(realClientId);
logger.info("Connect to exsisting client (on-line): " + client.getId(), clientId);
} else {
// Get Client id from his container
client.setId(cContainer.getClientId());
logger.info("Connect to exsisting client (off-line): " + client.getId(), clientId);
}
logger.info("Client is already connected: " + clientId, clientId);
cContainer.setWelcomeMessage(false);
69
} else {
logger.info("Connected new client: " + clientId, clientId);
cContainer = new ClientContainer();
cContainer.setWelcomeMessage(true);
}
// Actualize client connections for using it with response
cContainer.setClientId(client.getId());
cContainer.setConnection(conn);
connections.put(clientId, cContainer);
// Send greathings message for client
IServiceCapableConnection sc =
(IServiceCapableConnection)connections.get(clientId).getConnection();
if (sc != null) {
sc.invoke("connectionFromServer");
}
return true;
}
/**
* Disconnect client.
*
* {@inheritDoc}
* @param conn IConnection
* @param scope IScope
*/
@Override
public void disconnect(IConnection conn, IScope scope) {
String clientId =
conn.getClient().getAttribute(ClientAttribute.CONNECTION_ID.toString()).toString();
logger.info("Disconnecting client: " + clientId);
super.disconnect(conn, scope);
}
/**
* Gets the client id.
*
* @return the client id
*/
public String getClientId(String connectionId) {
for (IClient client : getClients()) {
if (client.getAttribute(ClientAttribute.CONNECTION_ID.toString()).equals(connectionId)) {
return (String)client.getAttribute(ClientAttribute.CONNECTION_ID.toString());
}
}
logger.debug("Client id: " + Red5.getConnectionLocal().getClient().getId());
return Red5.getConnectionLocal().getClient().getId();
}
/**
* Generate new unique stream name.
*
70
* @return Stream name
*/
public String getStreamName() {
logger.info("getting a stream name " + streamCounter);
return String.valueOf(streamCounter++);
}
/**
* Execute command into Brain.
*
* @param emotions Avatar emotions
*/
public synchronized void executeCommand(ResponseToFlex emotions) {
if (emotions == null) {
return;
}
Question question = new Question();
question.setE1(emotions.getE1());
question.setE2(emotions.getE2());
question.setE3(emotions.getE3());
question.setE4(emotions.getE4());
IClient client = Red5.getConnectionLocal().getClient();
String clientId = (String)client.getAttribute(ClientAttribute.CONNECTION_ID.toString());
if (connections.containsKey(clientId)) {
ClientContainer cContainer = connections.get(clientId);
if (cContainer.isWelcomeMessage()) {
question.setMessage("welcome");
cContainer.setWelcomeMessage(false);
} else {
question.setMessage("welcome back");
}
} else {
question.setMessage("welcome");
}
this.speech.askBrain(question);
}
/**
* Start live stream recording.
*
* @param stream IBroadcastStream
*/
@Override
public void streamRecordStart(IBroadcastStream stream) {
logger.debug("START Recording stream (RT)");
try {
stream.saveAs(stream.getPublishedName(), false);
} catch (Exception e) {
e.printStackTrace();
}
super.streamRecordStart(stream);
71
}
/**
* Stop live stream recording.
* Convert recorded audio from flv to wav.
* Recognize command from speech, ask Brain
* and send Answer into client.
*
* @param stream IBroadcastStream
*/
@Override
public void streamBroadcastClose(IBroadcastStream stream) {
logger.debug("STOP Recording stream (RT)");
String file = Settings.STREAM_FOLDER_PATH + stream.getPublishedName();
IMediaReader reader = ToolFactory.makeReader(file + ".flv");
ConvertAudio ca = new ConvertAudio(reader, file + "_in.wav");
ca.setSpeech(speech);
ca.run();
super.streamBroadcastClose(stream);
}
/**
* Start play live stream.
*
* @param stream ISubscriberStream
*/
@Override
public void streamSubscriberStart(ISubscriberStream stream) {
IConnection conn = stream.getConnection();
IClient client = conn.getClient();
logger.debug("START Playing stream (RT)",
client.getAttribute(ClientAttribute.CONNECTION_ID.toString()).toString());
super.streamSubscriberStart(stream);
}
/**
* Stop playing live stream.
*
* @param stream ISubscriberStream
*/
@Override
public void streamSubscriberClose(ISubscriberStream stream) {
IConnection conn = stream.getConnection();
IClient client = conn.getClient();
logger.debug("STOP Playing stream (RT)", client.getId());
super.streamSubscriberClose(stream);
}
}
72
SpeechServer\src\com\janklimczak\speech\server\recognize\SpeechRecognize.java:
package com.janklimczak.speech.server.recognize;
import com.janklimczak.speech.server.utils.SpeechLogger;
import java.io.File;
import java.net.URL;
import java.util.LinkedList;
import edu.cmu.sphinx.frontend.util.AudioFileDataSource;
import edu.cmu.sphinx.recognizer.Recognizer;
import edu.cmu.sphinx.result.Result;
import edu.cmu.sphinx.util.props.ConfigurationManager;
/**
* Speech recognizer.
*/
public class SpeechRecognize {
/** Logger */
private SpeechLogger logger = new SpeechLogger(SpeechRecognize.class);
/** URL of file to recognize from */
private URL urlAudio;
/** Recognize configuration */
private ConfigurationManager cm;
/** Recognizer */
private Recognizer recognizer;
/** Audio data source */
private AudioFileDataSource audioDataSource;
/** Recognition result */
private Result result;
/**
* Initialize and allocate speech recognizer.
*
*/
public void initialize() {
logger.info("Initialize Recognizer");
URL urlConfig = SpeechRecognize.class.getResource("config.xml");
this.cm = new ConfigurationManager(urlConfig);
this.recognizer = (Recognizer)cm.lookup("recognizer");
this.recognizer.allocate();
this.audioDataSource = (AudioFileDataSource)cm.lookup("audioFileDataSource");
logger.info("Recognizer initialized");
}
/**
73
* Recognize speech into one sentence.
*
* @param file File to recognize.
* @return Recognized text
*/
public String recognizeFull(String file) {
LinkedList<String> recognized = recognize(file);
if (recognized == null) {
return null;
} else {
String result = "";
for (String s : recognized) {
result += " " + s.trim() + " ";
}
return result.trim();
}
}
/**
* Recognize text from file.
*
* @param file File to recognize
* @return Recognized word list
*/
public LinkedList<String> recognize(String file) {
LinkedList<String> resultSpeeak = new LinkedList<String>();
logger.info("Start recognition");
File myfile = new File(file);
System.out.println("Open: " + file);
if (!myfile.exists()) {
logger.error("file not exsists");
}
if (!myfile.canRead()) {
logger.error("cant read recorded file");
}
audioDataSource.setAudioFile(myfile, null);
boolean none = false;
while ((result = recognizer.recognize()) != null) {
String resultText = result.getBestResultNoFiller();
if (resultText != null && !resultText.isEmpty()) {
logger.info("You said: " + resultText);
resultSpeeak.add(resultText);
} else {
logger.info("I don't understand You.");
none = true;
}
}
logger.info("Finish recognition");
74
if (none) {
return null;
} else {
return resultSpeeak;
}
}
/**
* Deinitialize speech recognizer.
*/
public void deinitialize() {
this.recognizer.deallocate();
}
/**
* Get speech recognizing result.
*
* @return Speech recognizing result
*/
public Result getResult() {
return result;
}
}
}
SpeechServer\src\com\janklimczak\speech\server\speech\Speech.java:
package com.janklimczak.speech.server.speech;
import com.janklimczak.speech.brain.client.BrainServicePortClient;
import com.janklimczak.speech.brain.client.model.Answer;
import com.janklimczak.speech.brain.client.model.Question;
import com.janklimczak.speech.server.client.ClientAttribute;
import com.janklimczak.speech.server.recognize.SpeechRecognize;
import com.janklimczak.speech.server.syntetize.SpeechSyntetizer;
import com.janklimczak.speech.server.utils.SpeechLogger;
import java.util.LinkedList;
import org.red5.io.utils.ObjectMap;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.service.ServiceUtils;
/**
* Speech service.
*
* @author Jan Klimczak
*/
public class Speech implements IPendingServiceCallback {
/** Logger */
private static SpeechLogger logger = new SpeechLogger(Speech.class);
/** Brain service client */
private static BrainServicePortClient brainService = new BrainServicePortClient();
75
/** Speech recognizer */
private SpeechRecognize recognizer = new SpeechRecognize();
/** Speech syntetizer */
private SpeechSyntetizer syntetizer = new SpeechSyntetizer();
/** Stream counter */
private int streamCounter = 0;
/** Result */
private String result;
/** Client id */
private String clientId;
/**
* Initialize speech service.
*/
public Speech() {
super();
this.initialize();
}
/**
* Initialize speech syntetizer and recognizer.
*/
public void initialize() {
this.syntetizer.initialize();
this.recognizer.initialize();
}
/**
* Get Avatar emotions from client response.
* Ask Brain.
*
* @param call IPendingServiceCall
*/
@Override
public void resultReceived(IPendingServiceCall call) {
call.unregisterCallback(this);
Question question = new Question();
question.setMessage(result);
if (call.isSuccess()) {
ObjectMap emotions = (ObjectMap)call.getResult();
question.setE1((Integer)emotions.get("e1"));
question.setE2((Integer)emotions.get("e2"));
question.setE3((Integer)emotions.get("e3"));
question.setE4((Integer)emotions.get("e4"));
logger.debug("Load Avatar state into Server for Brain.");
askBrain(question);
} else {
logger.error("Cannot get Awatar state from client.");
76
askBrain(question);
}
}
/**
* Ask Brain by web-servcie.
* Get Avatar emotions from client.
*
* @param question Question to ask.
*/
public void askBrain(Question question) {
IConnection conn = Red5.getConnectionLocal();
Object requestUrl = conn.getConnectParams().get("pageUrl");
if (requestUrl != null) {
question.setUrl(requestUrl.toString());
}
Answer answer = brainService.askQuestion(question);
ResponseToFlex response = new ResponseToFlex();
response.readBrainAnswer(answer);
ServiceUtils.invokeOnConnection("responseFromServer", new Object[] { response });
syntetizer.speechNow(clientId, answer, getStreamName());
}
/**
* Generate speech.
*
* @param clientId Client id
* @param text Text to generate speech
* @param streamName Stream name to save file
*/
public void speechNow(String clientId, String text, String streamName) {
this.syntetizer.speechNow(clientId, text, streamName);
}
/**
* Generate speech.
*
* @param clientId Client id
* @param answer Answer
* @param streamName Stream name to save file
*/
public void speechNow(String clientId, Answer answer, String streamName) {
this.syntetizer.speechNow(clientId, answer, streamName);
}
/**
* Recognize text from audio file.
*
* @param file File to recognize
* @return Recognize word list
*/
public LinkedList<String> recognize(String file) {
return this.recognizer.recognize(file);
}
/**
77
* Recognize text from audio file and ask Brain.
*
* @param file File to recognize
*/
public void recognizeAndExecute(String file) {
result = this.recognizer.recognizeFull(file);
clientId =
(String)Red5.getConnectionLocal().getClient().getAttribute(ClientAttribute.CONNECTION_ID.toString());
IServiceCapableConnection sc = (IServiceCapableConnection)Red5.getConnectionLocal();
if (sc != null) {
sc.invoke("getAwatarState", this);
} else {
Question question = new Question();
question.setMessage(result);
askBrain(question);
}
}
/**
* Generate unique stream name.
*
* @return Stream name.
*/
private String getStreamName() {
logger.info("getting a stream name " + streamCounter);
return String.valueOf(streamCounter++ + "_out");
}
}
SpeechServer\src\com\janklimczak\speech\server\syntetize\SpeechSyntetizer.java:
package com.janklimczak.speech.server.syntetize;
import com.janklimczak.speech.brain.client.model.Answer;
import com.janklimczak.speech.server.Settings;
import com.janklimczak.speech.server.audio.ConvertAudio;
import com.janklimczak.speech.server.audio.SaveToFile;
import com.janklimczak.speech.server.utils.SpeechLogger;
import org.red5.server.api.service.ServiceUtils;
import com.sun.speech.freetts.FreeTTS;
import com.sun.speech.freetts.Voice;
import com.sun.speech.freetts.VoiceManager;
import com.xuggle.mediatool.IMediaReader;
import com.xuggle.mediatool.ToolFactory;
/**
* Speech syntetizer.
*
* @author Jan Klimczak
*/
public class SpeechSyntetizer {
/** Voice */
78
private Voice voice;
/** FreeTTS engine */
private FreeTTS freetts;
/** Voice manager */
private VoiceManager vm;
/** Logger */
private SpeechLogger logger = new SpeechLogger(SpeechSyntetizer.class);
/**
* Initialize speech synthetizer.
*/
public void initialize() {
logger.info("Initialize Syntetizer");
System.setProperty("com.sun.speech.freetts.voice.defaultAudioPlayer",
"com.janklimczak.speech.server.audio.SaveToFile");
this.vm = VoiceManager.getInstance();
this.voice = vm.getVoice("kevin16");
this.logger.debug("Initialize Voice");
if (voice == null) {
this.logger.error("Voice is null");
throw new RuntimeException("Voice is null");
}
if (voice != null) {
this.voice.allocate();
}
this.freetts = new FreeTTS(voice);
logger.info("Syntetizer initialized");
}
/**
* Denitialize speech synthetizer.
*/
public void deinitialize() {
logger.info("De-Initialize Syntetizer");
this.freetts.shutdown();
}
/**
* Speech.
*
* @param clientId Client id.
* @param text Text to speech.
* @param streamName Stream name to save file.
*/
public void speech(String clientId, String text, String streamName) {
Answer answer = new Answer();
answer.setMessage(text);
this.speech(clientId, answer, streamName);
79
}
/**
* Speech with emotions.
* Convert wav to mp3 in new thread.
* Send request to speak for client.
*
* @param clientId Client id.
* @param answer Text to speech from Answer.
* @param streamName Stream name to save file.
*/
public void speech(String clientId, Answer answer, String streamName) {
String text = answer.getMessage();
SaveToFile sfap;
byte b[] = text.getBytes();
if (b == null) {
logger.error("No byte array", clientId);
return;
}
String file = Settings.STREAM_FOLDER_PATH + streamName;
IMediaReader reader = null;
ConvertAudio ca;
try {
sfap = (SaveToFile)voice.getDefaultAudioPlayer();
sfap.reset();
sfap.setSpeechLength(b.length);
sfap.setFileName(file + ".wav");
sfap.write(b);
this.logger.debug("Generate message", clientId);
float volume = (float)0.85;
float pitch = 100;
float duration = 1;
float pitchShift = 1;
float pitchRange = 1;
float rate = 120;
// Generate emotions 1
if (answer.getStateE1() != null) {
Integer e1 = answer.getStateE1();
// ------- Volume
float newVolume;
if (e1 > 100) {
newVolume = (float)((e1 - 100) + 1) / 100;
volume -= newVolume;
} else {
newVolume = (float)(e1 + 1) / 100;
volume += newVolume;
}
// ------- Pitch
80
float newPitch;
if (e1 > 100) {
newPitch = (float)((e1 - 100) + 1) / 3;
pitch -= newPitch;
} else {
pitch = e1;
}
}
// Generate emotions 2
if (answer.getStateE2() != null) {
Integer e2 = answer.getStateE2();
// ------- Duration
float newDuration;
if (e2 > 100) {
newDuration = (float)((e2 - 100) + 1) / 100;
duration += newDuration;
} else {
newDuration = (float)((e2 - 100) + 1) / 200;
duration -= newDuration;
}
}
// Generate emotions 3
if (answer.getStateE3() != null) {
Integer e3 = answer.getStateE3();
// ------- Pitch Shift
float newPitchShift;
if (e3 > 100) {
newPitchShift = (float)((e3 - 100) + 1) / 75;
pitchShift += newPitchShift;
} else {
newPitchShift = (float)((e3 - 100) + 1) / 250;
pitchShift -= newPitchShift;
}
}
// Generate emotions 4
if (answer.getStateE4() != null) {
Integer e4 = answer.getStateE4();
// ------- Pitch Range
//pitchRange = 101 - e4;
pitchRange = 1;
// ------- Rate
float newRate;
if (e4 > 100) {
newRate = (float)((e4 - 100) + 1) / 2;
rate -= newRate;
} else {
newRate = (float)((e4 - 100) + 1) * 2;
rate += newRate;
}
81
}
this.voice.setVolume(volume);
this.voice.setPitch(pitch);
this.voice.setDurationStretch(duration);
this.voice.setPitchShift(pitchShift);
this.voice.setPitchRange(pitchRange);
this.voice.setRate(rate);
this.voice.speak(text);
sfap.close();
this.logger.debug("Save generated message to server", clientId);
this.logger.debug("Start converting message to stream", clientId);
reader = ToolFactory.makeReader(file + ".wav");
reader.open();
ca = new ConvertAudio(reader, file + ".mp3");
ca.run();
} catch (Exception e) {
reader.close();
e.printStackTrace();
this.logger.error("Error generating message", clientId);
}
}
/**
* Speech now and request it into client.
*
* @param clientId Client id
* @param answer Answer
* @param streamName File to save
*/
public void speechNow(String clientId, Answer answer, String streamName) {
this.speech(clientId, answer, streamName);
Object[] param = new Object[1];
param[0] = streamName;
ServiceUtils.invokeOnConnection("speak", param);
}
/**
* Speech now and request it into client.
*
* @param clientId Client id
* @param text Text to speech
* @param streamName File to save
*/
public void speechNow(String clientId, String text, String streamName) {
Answer answer = new Answer();
answer.setMessage(text);
this.speechNow(clientId, answer, streamName);
82
}
}
Klient - SpeechAvatar
SpeechAvatar\src\com\janklimczak\speech\SpeechEngine.as:
package com.janklimczak.speech
{
import com.janklimczak.speech.avatar.Avatar
import com.janklimczak.speech.Settings;
import com.janklimczak.speech.communication.CommunicateWithServer;
import com.janklimczak.speech.communication.ServiceConnector;
import com.janklimczak.speech.device.SpeechMicrophone;
import com.janklimczak.speech.state.AwatarState;
import com.janklimczak.speech.stream.Stream;
import com.janklimczak.speech.utils.Logger;
import flash.events.NetStatusEvent;
import flash.net.Responder;
import mx.controls.Alert;
import mx.core.FlexGlobals;
import mx.events.FlexEvent;
import mx.utils.OnDemandEventDispatcher;
/**
* Main Avatar Client Engine.
*
* @author Jan Klimczak
*
*/
public class SpeechEngine
{
/** Avatar state - store and manage Avatar emotions */
[Bindable]
public var awatarState:AwatarState;
/** Graphic Avatar instance */
[Bindable]
public var avatar:Avatar;
/** Manage communication with Speech Server */
public var communicateWithServer:CommunicateWithServer;
/** Logger */
public var logger:Logger;
/** Application settings */
private var settings:Settings;
/** Manage live streams with Speech Server */
private var stream:Stream;
83
/** Client microphone instance */
private var microphone:SpeechMicrophone;
// INITIALIZE ------------------------------------------------------------------
/**
* Initialize whole Client Avatar engine.
*
*/
public function SpeechEngine() {
this.initializeEngine();
this.awatarState.makeLive();
this.communicateWithServer.connId = this.settings.getClientId();
this.communicateWithServer.connectToService(onConnectionNetStatus);
this.initializeDevices();
}
/**
* Initialize engine.
*
* Make Avatar alive (forgetting function).
* Connect with Speech Server.
*
*/
private function initializeEngine():void {
this.logger = new Logger();
this.logger.openMessageLog();
this.microphone = new SpeechMicrophone();
this.settings = new Settings();
this.awatarState = new AwatarState();
this.communicateWithServer = new
CommunicateWithServer(this.settings.data.rtmpService, this.awatarState);
}
/**
* Initialize microphone.
*/
private function initializeDevices():void {
this.microphone.initialize();
}
/**
* Initialize live stream and connect with Speech Server.
*/
private function initializeStream():void {
this.stream = new Stream(this.communicateWithServer.connection);
this.stream.initialize(this.microphone.mic, onStreamStatus);
this.communicateWithServer.setStream(this.stream);
}
84
// GLOBAL LISTNERS ------------------------------------------------------------
/**
* Awaiting connection with Speech Server.
* When connected initialize stream.
*
* @param event NetStatusEvent
*
*/
private function onConnectionNetStatus(event:NetStatusEvent) : void {
if(event.info.code == "NetConnection.Connect.Success") {
logger.info('Successfull connected to RTMP server');
this.communicateWithServer.connectionStatus = true;
this.initializeStream();
} else {
logger.error('Error: connect to RTMP server (' + event.info.code + ')');
}
}
/**
* Awaiting stream status and log that into logger.
*
* @param e NetStatusEvent
*
*/
private function onStreamStatus(e:NetStatusEvent) : void {
if ( e.info.code == "NetStream.Publish.Start" ) {
logger.info('Started recording to server (' + e.info.code + ')');
} else if (e.info.code == "NetStream.Record.NoAccess") {
logger.error('You don\'t have access to microphone');
}
this.logger.debug( e.info.code );
}
// ENGINE ---------------------------------------------------------------------
/**
* Start recording speeak into Speech Server.
*
*/
public function recordStart_clickHandler():void
{
logger.info("Initialize START Recording");
// if a stream is already playing, close it
if ( this.stream.getNetStream() != null ) this.stream.getNetStream().close();
this.stream.getNetStream().publish(communicateWithServer.connId, "record");
}
/**
* Stop recording speeak into Speech Server.
*
*/
85
public function recordStop_clickHandler():void
{
logger.info("Initialize STOP Recording");
if ( this.stream.getNetStream() != null ) this.stream.getNetStream().close( );
}
}
}
SpeechAvatar\src\com\janklimczak\speech\avatar\AvatarEmotions.as:
package com.janklimczak.speech.avatar
{
import com.janklimczak.speech.avatar.Avatar;
import com.janklimczak.speech.state.AwatarState;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.geom.ColorTransform;
import flash.utils.Timer;
/**
* Animate avatar emotions into Graphic Avatar instance.
*
* @author Jan Klimczak
*
*/
public class AvatarEmotions {
/** Graphic Avatar instance */
[Bindable]
public var avatar:Avatar;
/** Avatar state,emotions */
[Bindable]
public var state:AwatarState;
/** Counter */
public var counter:int = 0;
/** Timer used for animating changes */
private var myTimer:Timer = new Timer(25);
/** Is Avatar curently speaking */
private var speaking:Boolean = false;
/** Factor of speed animation */
private var speed:int = 10;
/**
* Initialize and start timers for animating.
*
* @param avatar Graphic Avatar instance.
*
*/
public function AvatarEmotions(avatar:Avatar) {
this.avatar = avatar;
86
this.myTimer.start();
this.myTimer.addEventListener(TimerEvent.TIMER, timerHandler);
}
/**
* Animate mouth every frame.
* Use speed factor to make it slower or faster.
*
* @param e TimerEvent
*
*/
private function timerHandler(e:TimerEvent):void {
if (this.avatar.isLoaded) {
if (this.speaking) {
if (this.counter > (speed/4) ) {
this.avatar.mouth.nextFrame();
this.counter = 0;
} else {
this.counter++;
}
} else {
if (this.avatar.mouth.currentFrame > 50) {
this.avatar.mouth.gotoAndStop(1);
} else if (this.avatar.mouth.currentFrame != 1) {
this.avatar.mouth.nextFrame();
}
}
}
}
/**
* Connect Avatar state with emotions into this module.
*
* @param state Awatar state
*
*/
public function connectState(state:AwatarState):void {
this.state = state;
}
/**
* Update Graphic Avatar to display changes for client.
*
*/
public function updateGraphicAvatar():void {
if (this.avatar.isLoaded) {
this.updateSatisfaction();
this.updateExcitement();
this.updateJoy();
}
}
87
/**
* Calculate satisfaction changes based on Avatar state.
*
*/
private function updateSatisfaction():void {
var factor:int = this.state.e2;
var newColor:ColorTransform = new ColorTransform();
newColor.redMultiplier = 0;
newColor.redOffset = factor + 155;
newColor.greenMultiplier = 0;
newColor.greenOffset = factor + 100;
newColor.blueMultiplier = 0;
newColor.blueOffset = factor + 70;
this.avatar.face.transform.colorTransform = newColor;
}
/**
* Calculate excitement changes based on Avatar state.
*
*/
private function updateExcitement():void {
var factor:Number = (this.state.e3 + 1) / 100 ;
factor = Math.abs(factor - 2);
this.avatar.eyeLeft.scaleY = factor;
this.avatar.eyeRight.scaleY = factor;
}
/**
* Calculate joy changes based on Avatar state.
*
*/
private function updateJoy():void {
this.speed = ((this.state.e3 + 1) / 10) + 1;
}
/**
* Start speak.
*
*/
public function startSpeak():void {
this.speaking = true;
}
/**
* Stop speak.
*
*/
public function stopSpeak():void {
this.speaking = false;
}
}
}
SpeechAvatar\src\com\janklimczak\speech\communication\CommunicateWithServer.as:
88
package com.janklimczak.speech.communication
{
import com.janklimczak.speech.state.AwatarState;
/**
* Module responsible for bi-directional communication with Speech Server.
*
* @author Jan Klimczak
*
*/
public class CommunicateWithServer extends ResponseToServer {
/**
* Constructor.
*
* @param serviceAddress Address of RTMP service of Speech Server.
* @param awatarState Avatar state.
*
*/
public function CommunicateWithServer(serviceAddress:String, awatarState:AwatarState) {
super(serviceAddress, awatarState);
}
}
}
SpeechAvatar\src\com\janklimczak\speech\communication\ResponseFromServer.as:
package com.janklimczak.speech.communication
{
import com.janklimczak.speech.model.ResponseToFlex;
import com.janklimczak.speech.state.AwatarState;
import com.janklimczak.speech.state.Emotions;
import com.janklimczak.speech.utils.Logger;
import flash.events.AsyncErrorEvent;
import flash.events.NetStatusEvent;
import flash.events.TimerEvent;
import flash.net.NetStreamPlayOptions;
import flash.net.Responder;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
/**
* Manage responses from Speech Server.
* Reacting for response from server.
*
* @author Jan Klimczak
*
*/
public class ResponseFromServer extends ServiceConnector {
/** Timer for re-playing audio from Speech Server */
private var myTimer:Timer = new Timer(500, 5);
/** Streeam name to play from Speech Server */
private var sreamName:String = "0";
/**
89
* Constructor.
*
* @param serviceAddress Address of RTMP service of Speech Server.
* @param awatarState Avatar state.
*
*/
public function ResponseFromServer(serviceAddress:String, awatarState:AwatarState) {
super(serviceAddress, awatarState);
}
/**
* Request from Sppech Server sent after connect client.
* Send request to Speech Server to generate welcome message.
*
*/
public function connectionFromServer():void {
this.logger.info('Greathings from RTMP server', true);
var emotions:ResponseToFlex = new ResponseToFlex();
emotions.e1 = awatarState.e1;
emotions.e2 = awatarState.e2;
emotions.e3 = awatarState.e3;
emotions.e4 = awatarState.e4;
var responseFromServerResponder:Responder = new Responder(null);
this.connection.call("executeCommand", responseFromServerResponder, emotions);
}
/**
* Request spaek audio from Speech Server.
* 5 time re-try playing in time if engine is no ready.
*
* @param streamName Audio file to play
*/
public function speak(streamName:String):void {
this.sreamName = streamName;
this.logger.info("Request speak message from server");
if ( this.stream != null ) {
// fs a stream is already playing, close it
this.play();
} else {
this.logger.info("Net stream is not initialised yet, will try again soon");
myTimer.start();
myTimer.addEventListener(TimerEvent.TIMER, timerHandler);
myTimer.addEventListener(TimerEvent.TIMER_COMPLETE,
completeHandler);
}
}
/**
* Stop play audio.
*
*/
public function stop():void {
this.stream.getNetStream().close();
}
90
/**
* Get current Avatar State (emotions).
*
* @return Avatar Emotions
*
*/
public function getAwatarState():Emotions {
this.logger.info('Sending Awatar State into Server', true);
var emotions:Emotions = new Emotions();
emotions.e1 = awatarState.e1;
emotions.e2 = awatarState.e2;
emotions.e3 = awatarState.e3;
emotions.e4 = awatarState.e4;
return emotions;
}
/**
* Take response form Speech Server.
* This response is mainlz generated in the Brain.
*
* Change Avatar state and redirect to other page based on response.
*
* @param obj Message from Speech Server.
*
*/
public function responseFromServer(obj:*):void {
logger.debug("Get request message from the Brain", true);
var response:ResponseToFlex = new ResponseToFlex();
response.convertToResponseObject(obj);
if (response.isRedirect) {
redirectToPage(response.redirectPage);
logger.info('Redirect to page: ' + response.redirectPage);
}
awatarState.actualizeState(response);
}
/**
* Log message from Speech Server.
*
* @param message Message to log
* @param type Mesage tzpe
*
*/
public function messageFromServer(message:String, type:String = "info"):void {
switch (type) {
case "debug" : logger.debug(message, true); break;
case "info" : logger.info(message, true); break;
case "warning" : logger.warning(message, true); break;
case "error" : logger.error(message, true); break;
default: logger.info(message, true);
}
}
91
/**
* Feedback about playing audio file.
*
* @param message Audio file info
*
*/
public function onMetaData ( message:Object ):void {
logger.info("Fetched meta data about audio", true);
for ( var key:* in message ) {
switch (key) {
case "duration" : logger.debug( "Audio file duration : " + message[key]
, true); break;
}
}
}
/**
* Feedback about playing audio file.
*
* @param message Audio file info
*
*/
public function onPlayStatus ( message:Object ):void {
logger.info("Fetched audio status", true);
for ( var key:* in message ) {
switch (key) {
case "bytes" : logger.debug("Audio file size : " + message[key] , true);
break;
}
}
}
/**
* Other audio file info.
* Not implemented.
*
* @param message Audio file info
*
*/
public function onBWCheck ( message:Object ):void { }
/**
* Other audio file info.
* Not implemented.
*
* @param message Audio file info
*
*/
public function onBWDone ( message:Object ):void { }
/**
* Stop playing listener. Send command to Avatar
* to stop speaking (animation).
*
* @param event NetStatusEvent
*
*/
private function stopPlayingListener(event:NetStatusEvent):void {
92
if(event.info.code == "NetStream.Play.Complete"
|| event.info.code == "NetStream.Play.Stop"
|| event.info.code == "NetConnection.Connect.Closed") {
this.awatarState.avatarEmotions.stopSpeak();
}
}
/**
* Play audio from Speech Server in Real Time.
* Start and stop animate Avatar to speaking.
*
*/
private function play():void {
this.stream.getNetStream().close( );
this.stream.getNetStream().play('mp3:' + this.sreamName, -2, -1 );
this.stream.getNetStream().inBufferSeek = false;
this.stream.getNetStream().bufferTime = 0;
this.stream.getNetStream().addEventListener(NetStatusEvent.NET_STATUS,
stopPlayingListener);
this.awatarState.avatarEmotions.startSpeak();
}
/**
* Try to speak again.
*
* @param e TimerEvent
*
*/
private function timerHandler(e:TimerEvent):void{
this.logger.info("Try to speak again");
if ( this.stream != null ) {
myTimer.stop();
myTimer.reset();
this.play();
}
}
/**
* Stop timer if is not possible to start play audio in specified time.
*
* @param e TimerEvent
*
*/
private function completeHandler(e:TimerEvent):void {
this.logger.error("Net stream is not initialised.");
myTimer.stop();
myTimer.reset();
}
}
}
93
SpeechAvatar\src\com\janklimczak\speech\state\AwatarState.as:
package com.janklimczak.speech.state
{
import com.janklimczak.speech.avatar.Avatar;
import com.janklimczak.speech.Settings;
import com.janklimczak.speech.avatar.AvatarEmotions;
import com.janklimczak.speech.model.ResponseToFlex;
import com.janklimczak.speech.utils.Logger;
import flash.events.TimerEvent;
import flash.external.ExternalInterface;
import flash.net.SharedObject;
import flash.utils.Timer;
/**
* Awatar state, emotions.
*
* @author Jan Klimczak
*
*/
public class AwatarState {
/**Happines */
[Bindable]
public var e1:int;
/** Satisfaction */
[Bindable]
public var e2:int;
/** Excitement */
[Bindable]
public var e3:int;
/** Joy */
[Bindable]
public var e4:int;
/** Avatar state, emotions instance */
public var avatarEmotions:AvatarEmotions;
/** Emotions limit */
private const E_LIMIT:int = 200;
/** Logger */
private var logger:Logger = new Logger();
/** Application settings */
private var settings:Settings;
/** Current timestamp */
private var timestamp:Date;
/** Container to store Avatar state, emotions */
private var so:SharedObject;
/**
* Initialize Avatar state, emotions.
94
* Create new emotions or load it if already exists.
*
*/
public function AwatarState():void {
this.so = SharedObject.getLocal("mind");
if (so.data.e == null) {
this.e1 = 100;
this.e2 = 100;
this.e3 = 100;
this.e4 = 100;
this.timestamp = new Date();
so.data.e = this;
so.flush();
} else {
this.e1 = this.so.data.e.e1;
this.e2 = this.so.data.e.e2;
this.e3 = this.so.data.e.e3;
this.e4 = this.so.data.e.e4;
this.timestamp = this.so.data.e.timestamp;
}
this.settings = new Settings();
if (this.settings.data.infoShowWindow) {
ExternalInterface.call('openWindowInfo');
}
}
/**
* Connect Avatar instance.
*
* @param avatar Avatar instance.
*
*/
public function connectAvatar(avatar:Avatar):void {
this.avatarEmotions = new AvatarEmotions(avatar);
this.avatarEmotions.connectState(this);
logger.info("Connected emotions to Graphic Awatar");
}
/**
* Update Avatar state, emotions.
*
* @param state Avatar state
*
*/
public function actualizeState(state:ResponseToFlex):void {
this.so = SharedObject.getLocal("mind");
if ((this.e1 += state.e1) <= E_LIMIT)
this.e1 = this.so.data.e.e1 += state.e1;
if ((this.e2 += state.e2) <= E_LIMIT)
this.e2 = this.so.data.e.e2 += state.e2;
if ((this.e3 += state.e3) <= E_LIMIT)
this.e3 = this.so.data.e.e3 += state.e3;
95
if ((this.e4 += state.e4) <= E_LIMIT)
this.e4 = this.so.data.e.e4 += state.e4;
this.timestamp = this.so.data.e.timestamp = new Date();
so.flush();
logger.debug("Emotion/State 1 update : " + state.e1, true);
logger.debug("Emotion/State 2 update : " + state.e2, true);
logger.debug("Emotion/State 3 update : " + state.e3, true);
logger.debug("Emotion/State 4 update : " + state.e4, true);
}
/**
* Make live for Avatar.
* Forgetting function.
*
*/
public function makeLive():void {
var myTimer:Timer = new Timer(500);
myTimer.addEventListener("timer", timerHandler);
myTimer.start();
}
/**
* Check if client is in Normal State.
*
* @return true if is in normal state othervise return false
*/
private function isNormalized():Boolean {
if (this.e1 == 100 && this.e2 == 100 && this.e3 == 100 && this.e4 == 100) {
return true;
} else {
return false;
}
}
/**
* Calculate next normalized factor.
*
* @return calculated normalize factor based on current e.
*/
private function normalizeMind(e:int):int {
if (e >= 150) {
return -2;
} else if (e >= 100) {
return -1;
} else if (e >= 50) {
return 1;
} else {
return 2;
}
}
/**
* Update Avatar state.
*
*/
private function changeClientState():void {
/**
96
* Based on current Client state make changes to client.
* If Client have bigger state then make bigger changes.
*
* Do it indentpendent for each mind state.
*/
this.e1 = this.so.data.e.e1 += normalizeMind(this.e1);
this.e2 = this.so.data.e.e2 += normalizeMind(this.e2);
this.e3 = this.so.data.e.e3 += normalizeMind(this.e3);
this.e4 = this.so.data.e.e4 += normalizeMind(this.e4);
this.timestamp = this.so.data.e.timestamp = new Date();
so.flush();
if (this.settings.data.infoShowWindow) {
ExternalInterface.call('updateE',this.e1,this.e2,this.e3,this.e4);
}
}
/**
* When active Client will back to normal state durng time process.
* Also Graphic Avatar is updated.
*
*/
public function timerHandler(event:TimerEvent):void {
this.changeClientState();
if (this.avatarEmotions != null) {
this.avatarEmotions.updateGraphicAvatar();
}
}
}
}
Model danych – SpeechModel
SpeechModel\src\com\janklimczak\speech\model\Pages.java:
package com.janklimczak.speech.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
/**
* Page entity.
*
* @author Jan Klimczak
*/
@Entity
@NamedQueries( { @NamedQuery(name = "Pages.findAll", query = "select o from Pages o") })
public class Pages implements Serializable {
97
/** Id */
@Id
@Column(nullable = false, length = 256)
private String url;
/** Page number */
@Column(name = "PAGE_NUMBER", nullable = false)
private Integer pageNumber;
public Pages() {}
public Pages(String url, Integer pageNumber) {
this.pageNumber = pageNumber;
this.url = url;
}
public Integer getPageNumber() {
return pageNumber;
}
public void setPageNumber(Integer pageNumber) {
this.pageNumber = pageNumber;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
SpeechModel\src\com\janklimczak\speech\model\Speech.java:
package com.janklimczak.speech.model;
import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
/**
* Speech entity.
*
* @author Jan Klimczak
*/
@Entity
98
@NamedQueries( { @NamedQuery(name = "Speech.findAll", query = "select o from Speech o") })
@Table(name = "SPEECH")
public class Speech implements Serializable {
/** Id */
@Id
@SequenceGenerator(name = "SPEECH_ID", sequenceName = "SPEECH_ID", allocationSize = 1)
@GeneratedValue(generator = "SPEECH_ID", strategy = GenerationType.SEQUENCE )
@Column(nullable = false)
private BigDecimal id;
/** Message/text */
@Column(nullable = false)
private String text;
/** Speech type */
@Column(nullable = false)
private SpeechType type;
/** Speech weight/importance */
@Column(nullable = false)
private SpeechWeight weight;
/** Corresponded pages */
@ManyToOne
@JoinColumn(name = "URL")
private Pages pages;
public Speech() {}
public Speech(Pages page, SpeechType type, SpeechWeight weight, String text) {
this.text = text;
this.type = type;
this.pages = page;
this.weight = weight;
}
public BigDecimal getId() {
return id;
}
public void setId(BigDecimal id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public SpeechType getType() {
return type;
}
public void setType(SpeechType type) {
this.type = type;
}
99
public SpeechWeight getWeight() {
return weight;
}
public void setWeight(SpeechWeight weight) {
this.weight = weight;
}
public Pages getPages() {
return pages;
}
public void setPages(Pages pages) {
this.pages = pages;
}
}
SpeechModel\src\com\janklimczak\speech\model\SpeechType.java:
package com.janklimczak.speech.model;
/**
* Speech type.
*
* @author Jan Klimczak
*/
public enum SpeechType {
MENU,
TITLE,
INTRO,
TEXT,
LINK,
PEOPLE,
SUBJECT,
SUBJECT_DATA
}
SpeechModel\src\com\janklimczak\speech\facade\Utils.java:
package com.janklimczak.speech.facade;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Utils {
public static SpeechFacade getSpeechFacade() throws NamingException {
final Context context = getInitialContext();
return (SpeechFacade)context.lookup("Brain-SpeechModel-
SpeechFacade#com.janklimczak.speech.facade.SpeechFacade");
}
public static Context getInitialContext() throws NamingException {
Hashtable env = new Hashtable();
// WebLogic Server 10.x connection details
env.put( Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory" );
env.put(Context.PROVIDER_URL, "t3://127.0.0.1:7001");
return new InitialContext( env );
100
}
}
Mózg – BrainService
BrainService\src\com\janklimczak\speech\brain\BrainService.java:
package com.janklimczak.speech.brain;
import com.janklimczak.speech.facade.SpeechFacade;
import com.janklimczak.speech.facade.Utils;
import com.janklimczak.speech.model.Message;
import com.janklimczak.speech.model.MessageType;
import java.util.Random;
import javax.jws.WebService;
import javax.naming.NamingException;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.BindingType;
import javax.xml.ws.soap.SOAPBinding;
/**
* Brain service.
*
* @author Jan Klimczak
*/
@WebService(serviceName = "BrainService", targetNamespace =
"http://com.janklimczak.speech.brainservice/",
portName = "BrainServicePort")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
@XmlSeeAlso( { Answer.class, Question.class })
public class BrainService {
/** Speech facade */
private SpeechFacade speechFacade;
/**
* Constructor.
*
* @throws NamingException Throw when is not posible to get object from JDNI address
*/
public BrainService() throws NamingException {
this.speechFacade = Utils.getSpeechFacade();
}
/**
* Ask question.
* Generate answer based on url, message and emotions.
*
* @param question Question
* @return Answer
*/
public Answer askQuestion(Question question) {
101
Answer answer = new Answer();
String message = question.getMessage();
if (message == null) {
generateAnswer(answer, MessageType.DONT_UNDERSTAND, 20, 10, 16, 22);
} else {
if (message.equals("good morning")) {
generateAnswer(answer, MessageType.WELCOME, 6, 9, 2, 8, false, false, false, true);
} else if (message.equals("welcome")) {
generateAnswer(answer, MessageType.WELCOME_ON_PORTAL, 36, 8, 6, 26, false, true, false,
true);
} else if (message.equals("welcome back")) {
generateAnswer(answer, MessageType.WELCOME_BACK, 40, 40, 40, 40, true, true, true, true);
} else if (message.equals("hello") || message.equals("how are you")) {
generateAnswer(answer, MessageType.HELLO, 9, 22, 4, 10, false, false, false, true);
} else if (message.equals("what is your name") || message.equals("name")) {
generateAnswer(answer, MessageType.MY_NAME_IS, 36, 36, 36, 36, true, true, true, true);
} else if (message.equals("read page") || message.equals("read") || message.equals("page")) {
generateAnswer(answer, MessageType.PAGE_ABOUT, 36, 36, 36, 36, true, true, true, true);
} else if (message.equals("next page") || message.equals("next")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
String nextPage = speechFacade.getPreviousPage(question.getUrl());
this.gotoPage(answer, "Next", nextPage);
} else if (message.equals("previous page") || message.equals("previous")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
String previousPage = speechFacade.getPreviousPage(question.getUrl());
this.gotoPage(answer, "Previous", previousPage);
} else if (message.equals("go home") || message.equals("home")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
this.gotoPage(answer, "Home", "http://94.23.252.15:7001/portal/index.xhtml");
} else if (message.equals("go people") || message.equals("people") || message.equals("teachers")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
this.gotoPage(answer, "People", "http://94.23.252.15:7001/portal/people.xhtml");
} else if (message.equals("go specialization") || message.equals("specialization")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
this.gotoPage(answer, "Specialization", "http://94.23.252.15:7001/portal/specialization.xhtml");
} else if (message.equals("go teaching") || message.equals("teaching")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
this.gotoPage(answer, "Teaching", "http://94.23.252.15:7001/portal/teaching.xhtml");
} else if (message.equals("go research") || message.equals("research")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
this.gotoPage(answer, "Research", "http://94.23.252.15:7001/portal/research.xhtml");
} else if (message.equals("go laboratories") || message.equals("laboratories")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
this.gotoPage(answer, "Laboratories", "http://94.23.252.15:7001/portal/laboratories.xhtml");
} else if (message.equals("smart control idea") || message.equals("smart control")) {
generateAnswer(answer, MessageType.GOTO_PAGE, 36, 36, 36, 36, true, true, true, true);
this.gotoPage(answer, "Smart Control Idea",
"http://94.23.252.15:7001/portal/smart_control_idea.xhtml");
} else {
generateAnswer(answer, MessageType.YOU_SAID, 36, 36, 36, 36, true, true, true, true);
answer.setMessage(answer.getMessage() + " " + message);
}
}
setEmotionStates(question, answer);
return answer;
}
102
/**
* Generate answer.
* Always generated emotions are positive.
*
* @param answer Answer
* @param message Speech message
* @param e1 Emotion 1
* @param e2 Emotion 2
* @param e3 Emotion 3
* @param e4 Emotion 4
*/
private void generateAnswer(Answer answer, MessageType message, Integer e1, Integer e2, Integer e3,
Integer e4) {
generateAnswer(answer, message, e1, e2, e3, e4, false, false, false, false);
}
/**
* Generate answer.
* You can set which emotions may be negative.
*
* @param answer Answer
* @param message Speech message
* @param e1 Emotion 1
* @param e2 Emotion 2
* @param e3 Emotion 3
* @param e4 Emotion 4
* @param isNegE1 Emotion 1 may be negative
* @param isNegE2 Emotion 2 may be negative
* @param isNegE3 Emotion 3 may be negative
* @param isNegE4 Emotion 4 may be negative
*/
private void generateAnswer(Answer answer, MessageType message, Integer e1, Integer e2, Integer e3,
Integer e4,
boolean isNegE1, boolean isNegE2, boolean isNegE3, boolean isNegE4) {
Random generator = new Random();
Integer newE1 = generator.nextInt(e1);
Integer newE2 = generator.nextInt(e2);
Integer newE3 = generator.nextInt(e3);
Integer newE4 = generator.nextInt(e4);
if (isNegE1) {
newE1 -= e1 / 2;
}
if (isNegE2) {
newE2 -= e1 / 2;
}
if (isNegE3) {
newE3 -= e1 / 2;
}
if (isNegE4) {
newE4 -= e1 / 2;
}
answer.setE1(newE1);
answer.setE2(newE2);
answer.setE3(newE3);
answer.setE4(newE4);
answer.setMessage(getMessage(message));
103
answer.setIsRedirect(false);
}
/**
* Set emotions.
*
* @param question Question
* @param answer Answer
*/
private void setEmotionStates(Question question, Answer answer) {
if (question == null && answer == null) {
return;
}
if (question.getE1() == null || question.getE2() == null || question.getE3() == null ||
question.getE4() == null) {
answer.setStateE1(answer.getE1());
answer.setStateE2(answer.getE2());
answer.setStateE3(answer.getE3());
answer.setStateE4(answer.getE4());
return;
}
if (answer.getE1() == null || answer.getE2() == null || answer.getE3() == null || answer.getE4() == null)
{
answer.setStateE1(question.getE1());
answer.setStateE2(question.getE2());
answer.setStateE3(question.getE3());
answer.setStateE4(question.getE4());
return;
}
answer.setStateE1(answer.getE1() + question.getE1());
answer.setStateE2(answer.getE2() + question.getE2());
answer.setStateE3(answer.getE3() + question.getE3());
answer.setStateE4(answer.getE4() + question.getE4());
}
/**
* Get text message based on specified message type.
*
* @param type Message type
* @return Text message
*/
private String getMessage(MessageType type) {
Message message = speechFacade.getMessage(type);
String result = message == null ? "" : message.getMessage();
return result;
}
/**
* Set redirect page.
*
* @param answer Answer
* @param pageName Redirected page name
104
* @param www URL address of redirected page
*/
private void gotoPage(Answer answer, String pageName, String www) {
answer.setMessage(answer.getMessage() + " " + pageName);
answer.setIsRedirect(true);
answer.setRedirectPage(www);
}
}
Administrator – Administrator
Administrator\src\com\janklimczak\speech\admin\SpeechAdmin.java:
package com.janklimczak.speech.admin;
import com.janklimczak.speech.facade.SpeechFacade;
import com.janklimczak.speech.facade.Utils;
import com.janklimczak.speech.model.Pages;
import com.janklimczak.speech.model.People;
import com.janklimczak.speech.model.Speech;
import com.janklimczak.speech.model.SpeechType;
import com.janklimczak.speech.model.SpeechWeight;
import com.janklimczak.speech.model.Subject;
import com.janklimczak.speech.model.SubjectData;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.LinkedList;
import javax.naming.NamingException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* Speech admin.
*
* @author Jan Klimczak
*/
public class SpeechAdmin {
/** Store parsed document */
private Document doc;
/** Store error messages prepared to display for user */
private LinkedList<String> errorMessages;
/** Sitemap URL */
private String sitemapUrl;
/** Spech facade. Is accessor to database */
private SpeechFacade speechFacade;
/**
105
* SpeechAdmin constructor.
*
* @param sitemapUrl Sitemap URL.
* @throws NamingException Error throw when is not posible to locate speech facade from JDNI address.
*/
public SpeechAdmin(String sitemapUrl) throws NamingException {
this.errorMessages = new LinkedList<String>();
this.sitemapUrl = sitemapUrl;
this.speechFacade = Utils.getSpeechFacade();
}
/**
* Genarate data for speech in database.
* First is clear database.
*
* @return Error messages.
* @throws MalformedURLException Throw when URL sitemap address cannot be resolved.
* @throws IOException Throw when cannot read sitemap file.
*/
public LinkedList<String> generateSpeechDB() throws MalformedURLException, IOException {
speechFacade.cleanSpeechDB();
LinkedList<String> webPages = SitemapUtils.getPageNamesFromSitemap(this.sitemapUrl);
int pageNumber = 0;
for (String page : webPages) {
Pages savedPage = speechFacade.persistPages(new Pages(page, pageNumber++));
try {
doc = Jsoup.connect(page).get();
Elements elements = doc.getElementsByTag("speech");
for (Element element : elements) {
try {
final String text = element.text();
final SpeechType type = SpeechType.valueOf(element.attr("value"));
switch (type) {
case PEOPLE:
savePeople(element, text);
break;
case SUBJECT:
saveSubject(element, text);
break;
case SUBJECT_DATA:
saveSubjectData(element);
break;
default:
SpeechWeight weight = getWeight(element);
saveSpeech(savedPage, type, weight, text);
}
} catch (IllegalArgumentException e) {
String msg = "Can't read ELEMENT: " + element;
errorMessages.add(msg);
}
}
106
} catch (IOException e) {
String msg = "Can't read PAGE: " + page;
errorMessages.add(msg);
} catch (IllegalArgumentException e) {
String msg = "Can't read PAGE: " + page;
errorMessages.add(msg);
}
}
return errorMessages;
}
/**
* Save speech data into database.
* Is based on speech tags incuded on the portal.
*
* @param savedPage Page url.
* @param type Speech type.
* @param weight Speech weight.
* @param text Speech text.
*/
private void saveSpeech(Pages savedPage, SpeechType type, SpeechWeight weight, String text) {
speechFacade.persistSpeech(new Speech(savedPage, type, weight, text));
}
/**
* Save speech people data into database.
* Is based on speech tags incuded on the portal.
*
* @param element Tag element.
* @param name People name.
*/
private void savePeople(Element element, String name) {
String degree = element.attr("degree");
String function = element.attr("function");
String moreUrl = element.attr("moreUrl");
People people = new People();
people.setName(name);
people.setFunction(function.trim().isEmpty() ? null : function);
people.setDegree(degree.trim().isEmpty() ? null : degree);
people.setUrlMore(moreUrl.trim().isEmpty() ? null : moreUrl);
speechFacade.persistPeople(people);
}
/**
* Save speech subject/specialisation name into database.
* Is based on speech tags incuded on the portal.
*
* @param element Tag element.
* @param name Subject/specialisation name.
*/
private void saveSubject(Element element, String name) {
107
Subject subject = speechFacade.getSubject(name);
if (subject != null) {
return;
}
String type = element.attr("type");
speechFacade.persistSubject(new Subject(name, type));
}
/**
* Save speech subject/specialisation data into database.
* Is based on speech tags incuded on the portal.
* The data is: name, subject form, number of hours, teachers, taught in, study level, semester and
department.
*
* @param element Tag element.
*/
private void saveSubjectData(Element element) {
String name = element.attr("name");
String subjectForm = element.attr("subjectForm".toLowerCase());
String noOfHours = element.attr("noOfHours".toLowerCase());
String teachers = element.attr("teachers");
String taughtIn = element.attr("taughtIn".toLowerCase());
String studyLevel = element.attr("studyLevel".toLowerCase());
String semester = element.attr("semester");
String department = element.attr("department");
SubjectData sData = new SubjectData();
Subject subject = new Subject();
subject.setName(name);
sData.setSubject(subject);
sData.setNoOfHours(new Integer(noOfHours));
sData.setSemester(semester);
sData.setStudyLevel(studyLevel);
sData.setSubjectForm(subjectForm);
sData.setTaughtIn(taughtIn);
sData.setTeachers(teachers);
sData.setDepartment(department);
speechFacade.persistSubjectData(sData);
}
/**
* Get speech weight stored in speech tag.
*
* @param element Tag element.
* @return Speech weigth.
*/
private SpeechWeight getWeight(Element element) {
SpeechWeight weight;
String w = element.attr("weight");
switch (new Integer(w)) {
case 1:
weight = SpeechWeight.SECOND;
break;
case 2:
108
weight = SpeechWeight.THIRD;
break;
case 0:
default:
weight = SpeechWeight.FIRST;
}
return weight;
}
}
Administrator\src\com\janklimczak\speech\admin\SitemapUtils.java:
package com.janklimczak.speech.admin;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedList;
/**
* Sitemap utils.
*
* @author Jan Klimczak
*/
public class SitemapUtils {
/**
* Gather page names from sitemap in list.
*
* @param sitemapUrl Sitemap URL.
* @return Sitemap page list.
* @throws MalformedURLException Throw when sitemap cannot be gathered from specified URL.
* @throws IOException Throw when is not possible to read sitemapo file.
*/
public static LinkedList<String> getPageNamesFromSitemap(String sitemapUrl) throws
MalformedURLException,
IOException {
URL url = new URL(sitemapUrl);
InputStream is = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
LinkedList<String> result = new LinkedList<String>();
String line = reader.readLine();
while (line != null) {
result.add(line);
}
return result;
}
}
109
Administrator\src\com\janklimczak\speech\admin\backing\Index.java:
package com.janklimczak.speech.admin.backing;
import com.janklimczak.speech.model.Message;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.component.html.HtmlCommandButton;
import javax.faces.component.html.HtmlForm;
import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlOutputLabel;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.naming.NamingException;
import java.io.Serializable;
import java.util.List;
/**
* Backing end for index page.
*
* @author Jan Klimczak
*/
@ManagedBean(name = "backing_index")
@SessionScoped
public class Index extends Utils implements Serializable {
/** HTML form */
private HtmlForm form;
/** Sitemap name */
private HtmlInputText sitemapName;
/** HTML generate button */
private HtmlCommandButton generateButton;
/** HTML show sitemap button */
private HtmlCommandButton showSitemapButton;
/** Info */
private HtmlOutputLabel info;
/** Speech messages container */
private ListDataModel model;
/**
* Get speech messages from database.
*
* @return Speech messages list
*/
public DataModel getList() {
try {
if (model == null) {
model = new ListDataModel();
connectToDb();
List<Message> list = getSpeechFacade().getMessageFindAll();
110
model.setWrappedData(list);
}
} catch (NamingException e) {
e.printStackTrace();
}
return model;
}
/**
* Edit current message action
*
* @return null
*/
public Object editAction() {
Message message = (Message)model.getRowData();
message.setEditable(true);
return null;
}
/**
* Reload speech messages from database.
*
* @return null
*/
public Object reloadAction() {
try {
model = new ListDataModel();
connectToDb();
List<Message> list = getSpeechFacade().getMessageFindAll();
model.setWrappedData(list);
} catch (NamingException e) {
e.printStackTrace();
}
return null;
}
/**
* Save speech message in database.
*
* @return null
*/
public Object saveAction() {
Message msg = (Message)model.getRowData();
getSpeechFacade().mergeMessage(msg);
msg.setEditable(false);
return null;
}
/**
* Delete message in database.
*
* @return null
111
*/
public Object deleteMessageAction(Message message) {
getSpeechFacade().removeMessage(message);
return null;
}
public void setForm(HtmlForm form1) {
this.form = form1;
}
public HtmlForm getForm() {
return form;
}
public void setSitemapName(HtmlInputText inputText1) {
this.sitemapName = inputText1;
}
public HtmlInputText getSitemapName() {
return sitemapName;
}
public void setGenerateButton(HtmlCommandButton commandButton1) {
this.generateButton = commandButton1;
}
public HtmlCommandButton getGenerateButton() {
return generateButton;
}
public void setShowSitemapButton(HtmlCommandButton showSitemapButton) {
this.showSitemapButton = showSitemapButton;
}
public HtmlCommandButton getShowSitemapButton() {
return showSitemapButton;
}
public void setInfo(HtmlOutputLabel info) {
this.info = info;
}
public HtmlOutputLabel getInfo() {
return info;
}
}
Portal demonstarcyjny – PortalPG
PortalPG\public_html\index.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition template="./WEB-INF/template/layout.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
112
xmlns:ezcomp="http://java.sun.com/jsf/composite/ezcomp">
<ui:define name="content">
<ezcomp:title value="About Department"/>
<h:graphicImage value="#{resource['image/department.jpg']}"/>
<ezcomp:text value="<speech value='INTRO' weight='0'> <b>The Department of Decision
Systems</b> was created in 2006 by professor Zdzislaw Kowalczuk and, of interests of the Department
concern modeling and identification, diagnostics, control and decision systems.</speech> Scientific
research involve planning and control both industrial and economical processes as well as developing modern
design methods using computer techniques and tools. The methods derived result both from classical analysis
and artificial/computational intelligence. Industrial process diagnosis, methods of system and signal analysis
and filtering, estimation of dynamical plants state, production and path planning and other problems, which
enhance the characterization of the Department, are utilized in automatic control systems, mobile and flying
robots, automotive vehicles and other industrial objects. In the field of teaching the Department’s staff
together with the staff of the Automatic Control Department play the role of a principal leader in the
BSc/MSc course of Automatic Control and Robotics in the Faculty of Electronics Telecommunication and
Informatics of the Gdansk University of Technology."/>
<ezcomp:endDocument/>
</ui:define>
</ui:composition>
PortalPG\public_html\avatar\avatar.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<script language="JavaScript" type="text/javascript">
<!--
// Version check for the Flash Player that has the ability to start Player Product Install (6.0r65)
var hasProductInstall = DetectFlashVer(6, 0, 65);
// Version check based upon the values defined in globals
var hasRequestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion,
requiredRevision);
if ( hasProductInstall && !hasRequestedVersion ) {
// DO NOT MODIFY THE FOLLOWING FOUR LINES
// Location visited after installation is complete if installation is required
var MMPlayerType = (isIE == true) ? "ActiveX" : "PlugIn";
var MMredirectURL = window.location;
//document.title = document.title.slice(0, 47) + " - Flash Player Installation";
var MMdoctitle = document.title;
AC_FL_RunContent(
"src", "playerProductInstall",
"FlashVars",
"MMredirectURL="+MMredirectURL+'&MMplayerType='+MMPlayerType+'&MMdoctitle='+MMdoctitle
+"",
"width", "100%",
"height", "100%",
"align", "middle",
113
"id", "SpeechAvatar",
"quality", "high",
"bgcolor", "#ffffff",
"name", "SpeechAvatar",
"allowScriptAccess","sameDomain",
"type", "application/x-shockwave-flash",
"pluginspage", "http://www.adobe.com/go/getflashplayer"
);
} else if (hasRequestedVersion) {
// if we've detected an acceptable version
// embed the Flash Content SWF when all tests are passed
/*AC_FL_RunContent(
"src", "#{request.contextPath}/avatar/SpeechAvatar.swf",
"flashVars", "client_id=#{session.id}",
"width", "215",
"height", "138",
"align", "middle",
"id", "SpeechAvatar",
"quality", "high",
"bgcolor", "#ffffff",
"name", "SpeechAvatar",
"allowScriptAccess","sameDomain",
"type", "application/x-shockwave-flash",
"pluginspage", "http://www.adobe.com/go/getflashplayer"
);*/
} else { // flash is too old or we can't detect the plugin
var alternateContent = ''
+ 'This content requires the Adobe Flash Player. '
+ '<a href=http://www.adobe.com/go/getflash/>Get Flash</a>';
document.write(alternateContent); // insert non-flash content
}
// -->
</script>
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
id="SpeechAvatar" width="215" height="138"
codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
<param name="movie" value="#{request.contextPath}/avatar/SpeechAvatar.swf" />
<param name='flashVars' value='client_id=#{session.id}'/>
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="allowScriptAccess" value="sameDomain" />
<embed src="#{request.contextPath}/avatar/SpeechAvatar.swf" quality="high"
bgcolor="#ffffff"
width="215" height="138" name="SpeechAvatar.swf" align="middle"
flashVars="client_id=#{session.id}"
play="true"
loop="false"
allowScriptAccess="sameDomain"
type="application/x-shockwave-flash"
pluginspage="http://www.adobe.com/go/getflashplayer">
</embed>
</object>
<noscript>
</noscript>
</ui:composition>
PortalPG\public_html\resources\ezcomp\menuItem.xhtml:
114
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html">
<!-- INTERFACE -->
<cc:interface>
<cc:attribute name="name" required="true"/>
<cc:attribute name="url" required="true"/>
<cc:attribute name="weight" default="0"/>
</cc:interface>
<!-- IMPLEMENTATION -->
<cc:implementation>
<h:outputLink value="#{cc.attrs.url}" title="#{cc.attrs.name}" styleClass="menuItem">
<speech value="MENU" weight="#{cc.attrs.weight}"><h:outputText
value="#{cc.attrs.name}"/></speech>
</h:outputLink>
</cc:implementation>
</html>
PortalPG\public_html\resources\ezcomp\text.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html">
<!-- INTERFACE -->
<cc:interface>
<cc:attribute name="value" required="true"/>
<cc:attribute name="weight" default="0"/>
</cc:interface>
<!-- IMPLEMENTATION -->
<cc:implementation>
<speech value="TITLE" weight="#{cc.attrs.weight}">
<h:outputText value="#{cc.attrs.value}" escape="false" styleClass="text"/>
</speech>
</cc:implementation>
</html>
PortalPG\public_html\resources\js\avatar.sj:
var isFirstLog = true, showLogLayer = false, showInfoLayer = false;
function openWindowLog() {
document.getElementById('logLayer').style.display = "block";
showLogLayer = true;
}
function closeWindowLog() {
document.getElementById('logLayer').style.display = "none";
showLogLayer = false;
115
}
function openWindowInfo() {
document.getElementById('infoLayer').style.display = "block";
showInfoLayer = true;
}
function closeWindowInfo() {
document.getElementById('infoLayer').style.display = "none";
showInfoLayer = false;
}
var infoE1, infoE2, infoE3, infoE4;
function updateE(e1, e2, e3, e4) {
if (showInfoLayer == false) {
return;
}
if (infoE1 == null)
infoE1 = document.getElementById('infoE1');
if (infoE2 == null)
infoE2 = document.getElementById('infoE2');
if (infoE3 == null)
infoE3 = document.getElementById('infoE3');
if (infoE4 == null)
infoE4 = document.getElementById('infoE4');
document.getElementById('e1table').style.height = (e1 + 30)/2;
document.getElementById('e2table').style.height = (e2 + 30)/2;
document.getElementById('e3table').style.height = (e3 + 30)/2;
document.getElementById('e4table').style.height = (e4 + 30)/2;
document.getElementById('e1table').height = (e1 + 30)/2;
document.getElementById('e2table').height = (e2 + 30)/2;
document.getElementById('e3table').height = (e3 + 30)/2;
document.getElementById('e4table').height = (e4 + 30)/2;
if (infoE1.hasChildNodes()) {
infoE1.removeChild(infoE1.firstChild);
}
if (infoE2.hasChildNodes()) {
infoE2.removeChild(infoE2.firstChild);
}
if (infoE3.hasChildNodes()) {
infoE3.removeChild(infoE3.firstChild);
}
if (infoE4.hasChildNodes()) {
infoE4.removeChild(infoE4.firstChild);
}
e1Node = document.createTextNode(e1);
e2Node = document.createTextNode(e2);
e3Node = document.createTextNode(e3);
e4Node = document.createTextNode(e4);
infoE1.appendChild(e1Node);
infoE2.appendChild(e2Node);
infoE3.appendChild(e3Node);
infoE4.appendChild(e4Node);
}
116
function addMessageToLog(msg, mode, isFromServer) {
if (showLogLayer == false) {
return;
}
dv = document.createElement('font');
txt = document.createTextNode(msg);
switch (mode) {
case 'info':
color = 'orange';
break;
case 'warning':
color = 'brown';
break;
case 'error':
color = 'red';
break;
case 'debug':
color = 'darkgreen';
break;
default :
color = 'black';
}
dv.setAttribute('color', color);
if (isFromServer) {
dv.setAttribute('style', 'display:block; font-weight: bold;');
}
else {
dv.setAttribute('style', 'display:block;');
}
dv.appendChild(txt);
logNode = document.getElementById('logMessage');
if (isFirstLog) {
logNode.appendChild(dv);
isFirstLog = false;
}
else {
fs = logNode.firstChild;
logNode.insertBefore(dv, fs);
}
}
PortalPG\public_html\WEB-INF\template\header.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets">
<title>Department of Decision Systems, Gdansk University of Technology, Poland</title>
<!--Meta Tags-->
117
<meta name="description" lang="en-us" content="Department of Decision Systems, Gdansk University
of Technology, Poland"/>
<meta name="keywords" lang="en-us" content="decision systems, decision, systems, ai, speech"/>
<meta http-equiv="Content-Language" content="en-us"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="all"/>
<meta http-equiv="refresh" content="public"/>
<meta name="Copyright" lang="en-us" content="Copyright (c) 2011 Gdansk University of Technology,
Poland"/>
<meta name="Author" lang="en-us" content="Jan Klimczak, Zdzislaw Kowalczuk"/>
<!--Styles-->
<link href="#{resource['css:default.css']}" rel="stylesheet" type="text/css" />
<link href="#{resource['css:cssLayout.css']}" rel="stylesheet" type="text/css" />
<link href="#{resource['css:ui-lightness/jquery-ui-1.8.16.custom.css']}" rel="stylesheet" type="text/css"
/>
<!--JQuery-->
<script src="#{resource['js:jquery-1.6.2.min.js']}" language="javascript" type="text/javascript"/>
<script src="#{resource['js:jquery-ui-1.8.16.custom.min.js']}" language="javascript"
type="text/javascript"/>
<!--Avatar-->
<script src="#{resource['js:avatar.js']}" language="javascript" type="text/javascript"/>
<!--Flash-->
<script src="#{resource['js:flash/AC_OETags.js']}" language="javascript" type="text/javascript"/>
<script src="#{resource['js:flash/requiredVersion.js']}" language="javascript" type="text/javascript"/>
<!--PNG Alpha in IE6 -->
<link href="#{resource['iepng:iepng.css']}" rel="stylesheet" type="text/css" />
<link href="#{resource['iepng:iepngfix_tilebg.js']}" rel="javascript" type="text/javascript" />
<!--Statistics - Google Analitics-->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-17253483-5']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-
analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</ui:composition>
PortalPG\public_html\WEB-INF\template\bottom.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
118
<div style="background: white;">
<table border="0" width="98%" align="center">
<tr>
<td width="215">
<div id="infoLayer" style="display:none;" >
<table width="100%" height="130">
<tr valign="bottom">
<td align="center" width="25%"> <table id="" bgcolor="yellow" width="50"
style="height:100;"><tr><td align="center" height="80"
id="e1table"><div>Happines</div></td></tr></table> </td>
<td align="center" width="25%"> <table id="" bgcolor="orange" width="50"
style="height:100;"><tr><td align="center" height="80"
id="e2table"><div>Satisfaction</div></td></tr></table> </td>
<td align="center" width="25%"> <table id="" bgcolor="red" width="50"
style="height:100;"><tr><td align="center" height="80"
id="e3table"><div>Excitement</div></td></tr></table> </td>
<td align="center" width="25%"> <table id="" bgcolor="brown" width="50"
style="height:100;"><tr><td align="center" height="80" id="e4table"><div>Joy</div></td></tr></table>
</td>
</tr>
<tr>
<td align="center"><div id="infoE1"></div></td>
<td align="center"><div id="infoE2"></div></td>
<td align="center"><div id="infoE3"></div></td>
<td align="center"><div id="infoE4"></div></td>
</tr>
</table>
</div>
</td>
<td width="215">
<ui:include src="../../avatar/avatar.xhtml"/>
</td>
<td width="*">
<div id="logLayer" style="display:none;" >
<div id="logMessage" style="height: 130px; overflow: auto; padding: 5px"></div>
</div>
</td>
</tr>
</table>
</div>
<div style="text-align:center; padding-top: 2px;">
<h:outputText value="All Rights Reserved ® 2011 by DecisionSystems.pl, Gdansk University of
Technology"/>
</div>
</ui:composition>
13.Bibliografia
[1] Huang Z., Eliëns H., Vissser C.: Programmability of Intelligent Agent Avatars. PDF
(http://wasp.cs.vu.nl/wasp/papers/avatar.pdf), Vrije University of Amsterdam, Montreal
(Canada) 2001.
[2] O'Shaughnessy D., Interacting With Computers by Voice: Automatic Speech Recognition
and Synthesis, vol. 91, no. 9. PDF (http://www.hoviat.dpsh.net/download/maghale-
signal/15.pdf), IEEE, Montreal (Canada) 2003.
[3] A. Kemble K., An Introduction to Speech Recognition. PDF
(ftp://ftp.software.ibm.com/software/pervasive/info/products/Introduction_to_Speech_Rec
ognition.pdf), IBM.
119
[4] Keller E., Keller B. Z., New Uses for Speech Synthesis, The Phonetician 81, ss. 35-40. PDF
(http://www.uniger.ch/BrigitteZellnerKeller/Brigitte_ZellnerKeller_files/Publications/Kell
er-ZellnerKeller-00-Phonetician81.pdf), Université de Lausanne, Lausanne (Switzerland)
2000.
[5] http: Microsoft Speech Technologies. http://msdn.microsoft.com/en-us/speech/default,
(2012).
[6] http: Java Speech API. http://java.sun.com/products/java-media/speech/, (2011).
[7] http: JavaTM Speech API Programmer's Guide, 1998. http://java.sun.com/products/java-
media/speech/forDevelopers/jsapi-guide/index.html, (2011).
[8] Bruce E.: Thinking in Java, (3nd edition). Helion, Gliwice 2003.
[9] http: White Papers. http://java.sun.com/products/java-
media/speech/reference/whitepapers/index.html, (2011).
[10] http: The Open Source Definition. http://www.opensource.org/docs/osd, (2011).
[11] http: Oracle Corporation. http://www.oracle.com, (2011).
[12] http: Apple. http://www.apple.com, (2011).
[13] http: AT&T. http://www.att.com, (2011).
[14] http: Dragon System. http://www.nuance.com, (2011).
[15] http: IBM Corporation. http://www.ibm.com, (2011).
[16] http: Novell. http://www.novell.com, (2011).
[17] http: Philips Electronics. http://www.philips.com, (2011).
[18] http: Texas Instruments. http://www.ti.com, (2011).
[19] http: JSR 113: JavaTM Speech API 2.0, 2009. http://www.jcp.org/en/jsr/detail?id=113,
(2011).
[20] http: Introduction and Overview of W3C Speech Interface Framework, 2000.
http://www.w3.org/TR/voice-intro/, (2011).
[21] http: Microsoft Speech API (SAPI) 5.3. http://msdn.microsoft.com/en-
us/library/ms723627(v=vs.85).aspx, (2011).
[22] http: Microsoft Tellme speech innovation. http://www.microsoft.com/en-
us/Tellme/default.aspx , (2011).
[23] http: Bell Laboratories, Inc. http://www.belllabs.com, (2011).
[24] http: Pioneering Speech Recognition.
http://www.ibm.com/ibm100/us/en/icons/speechreco/breakthroughs/, (2011).
[25] http: Speech Recognition Through the Decades: How We Ended Up With Siri, (2011).
http://www.pcworld.com/article/243060/speech_recognition_through_the_decades_how_w
e_ended_up_with_siri.html, (2011).
[26] http: Dragon Medical Solutions. http://www.nuance.com/products/dragon-medical-
family/index.htm, (2011).
[27] http: Dragon Speech Recognition Software. http://nuance.com/dragon/index.htm, (2011).
[28] http: TalkingJava SDK with Java Speech API implementation, (2011).
http://www.cloudgarden.com/JSAPI/, (2011).
[29] http: Acapela box (Elan Speech Cube). http://www.acapela-group.com/, (20110).
[30] http: Lumen Vox. http://www.lumenvox.com/, (2011).
[31] http: Ivona. http://www.ivona.com/en/, (2011).
[32] http: Sphinx-4, A speech recognizer written entirely in the JavaTM programming
language. http://cmusphinx.sourceforge.net/sphinx4/, (2011).
[33] http: Voice and Speech Recognition. http://www.e-speaking.com/, (2011).
[34] http: FreeTTS 1.2 - A speech synthesizer written entirely in the JavaTM programming
language. http://freetts.sourceforge.net/docs/index.php, (2011).
[35] http: Flite: a small, fast run time synthesis engine. http://www.speech.cs.cmu.edu/flite/,
(2011).
[36] http: The Festival Speech Synthesis System. http://www.cstr.ed.ac.uk/projects/festival/,
(2011).
120
[37] http: FestVox. http://festvox.org/, (2011).
[38] http: Real-Time Messaging Protocol (RTMP) specification.
http://www.adobe.com/devnet/rtmp.html, (2011).
[39] http: Adobe. http://www.adobe.com/, (2011).
[40] http: Adobe Flash Media Server family. http://www.adobe.com/products/flashmediaserver/,
(2011).
[41] http: Adobe LiveCycle Data Services ES3.
http://www.adobe.com/products/livecycle/dataservices/, (2011).
[42] http: Next-Generation Media Delivery Platform. http://www.wowza.com/, (2011).
[43] http: Red5. http://www.red5.org/, (2011).
[44] http: The Java EE 5 Tutorial. http://docs.oracle.com/javaee/5/tutorial/doc/, (2011).
[45] Burke B., Monson-Haefel R.: Enterprise JavaBeans 3.0. Helion, Gliwice 2007.
[46] http: Oracle Mojarra JavaServer Faces. http://javaserverfaces.java.net/, (2011).
[47] http: Facelets. http://facelets.java.net/, (2011).
[48] Goetz B., Peierls T., Bloch J., Bowbeer J., Holmes D., Lea D.: Java. Współbieżność dla
praktyków. Helion, Gliwice 2007.
[49] Pilone D., Pitman N.: UML 2.0 Alamanch. Helion, Gliwice 2007.
[50] Śmiałek M.: Zrozumieć UML 2.0. Helion, Gliwice 2005.
[51] Crawford W., Kaplan J.: J2EE Stosowanie wzorców projektowych. Helion, Gliwice 2004.
[52] McLaughlin B. D., Pollice G., West D.: Head First. Object-Oriented Analisys & Design.
Helion, Gliwice, 2008.
[53] Jayaswal B. K., Patton P. C.: Oprogramowanie godne zaufania. Helion, Gliwice 2007.
[54] Pilone D., Miles R.: Head First. Software development. Helion, Gliwice 2008.
[55] Mak G., Long J., Rubio D.: Spring Recipies. Apress, USA 2010.
[56] Kowalczuk Z., Czubenko M.: Dictobot – An autonomous agent with the ability to
communicate. Technologie Informacyjne, str. 87-92. Zeszyty naukowe WETI, 2011.