SEMSのいろいろ

打倒G*Voiceというわけではないが、とりあえず調べ始めました。 現時点での最新バージョンの1.1.1をFreeBSD 6.x(i386)で使っています。(2009/08/31)

SEMSはSERに参加していたiptego GmbHという会社のStefan Sayerさんなどが2006年に 始めたプロジェクトと思われる。2003の(c)が入っているコードもあるが、SERの関係 なのだろうか。

そもそも電話だけの機能であればVOIPではなく普通の電話で済ませば良い。明らか にVOIPは普通の電話に比べて通信資源のコストが大きい。ただMedia Serverのよう な仕組みは交換機ベースよりもIPベースの方が気の利いた仕組みが作りやすいし、 機材のコストが少ないと思う。

SEMSで出来そうな事は、

などなど。これらの組み合わせや、 ブラウザーやメールなどのインターネットの機能と連携して普通の電話では実現で きない事が提供できるのではないだろうか。

SERとSEMSは同じサイトが公開されているが、まったく作り込みが違う。 SERは完全にCのコードでthreadを使わずにプロセスベースで作られている。 SEMSはC++でthreadを使って作られている。 おそらくオリジナルの設計者が違うからなのではないだろうか。 SERはFOKUSのスタッフが始めたプロジェクトだがSEMSはSERに参加した、 IPTEGOという会社が始めたようだ。 唯一共通している事は両方ともgmake(BSD makeではなく)だけでビルドできconfigure を使っていない事だろうか。

クライアントを起動していないレジストしていない時の留守録などはレジストサーバ (Proxy)の設定に依存するので、SERとの連携が必須になる。単体サービスとして アクティブまたはパッシブに処理する場合はスタンドアローンでも処理できるのでは ないだろうか。

SERやSEMSはconfigureなどは使わずシンプルにgmake一発でビルドできるところが 気に入っている。またソースも他の依存がなく、依存のある部分(SQLやCodec)はプ ラグインなどで別建てで設計されているところも良い。ライブラリがなくビルドで きないモジュールなどもあるのでMakefileはエラーを無視するようになっている。

SEMSはGPLと商用ライセンスのどちらかなのだが、GPLの場合はサーバで動かすコード なので、自前で構築してサービスをおこなう場合は、ソースの公開義務は発生しない と考える。SIベンダーがSEMSをベースにシステムを開発してサービスプロバイダーに 販売する場合には商用ライセンスとしなければいけないが、あまり実効力があるとは 思えないのだが。

SEMSはSERなどの外部のSIPサーバと連携するための、binrpcctrlとunixsockctrl というプラグインと自前でSIPを処理するためのsipctrlというプラグラインが 用意されている。これらのどれかが設定されていないと起動できない。 sipctrlは1.0からのサポートで古いこれよりも前に作られた 「SEMS-ng design overview」などのドキュメントはSERとのソケット接続で 書かれていたりする。

まずはSERは使わずにシンプルにsipctrlで確認できる事を最初に確認したい。 sipctrlはFreeBSD 6.4の環境ではビルドでエラーが発生していた。 2つのファイルにヘッダーを追加したらsoがビルドできた。

1.1.1をFreeBSD 6.4(i386)で起動すると以下のようにSIGSEGVで落ちる。

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x814ea00 (LWP 100065)]
0x28258565 in __error () from /lib/libpthread.so.2
(gdb) where
#0  0x28258565 in __error () from /lib/libpthread.so.2
#1  0x2824b6bd in _pthread_mutex_trylock () from /lib/libpthread.so.2
#2  0x2824d820 in pthread_mutex_lock () from /lib/libpthread.so.2
#3  0x0810584b in AmMutex::lock (this=0x817f078) at AmThread.cpp:48
#4  0x080a79d7 in AmMediaProcessorThread::run (this=0x817e000) at AmThread.h:95
#5  0x08106071 in AmThread::_start (_t=0x817e000) at AmThread.cpp:70
#6  0x28248449 in pthread_create () from /lib/libpthread.so.2
#7  0x28306ecb in _ctx_start () from /lib/libc.so.6

ループの中でstopのフラグをmutexかけて拾うところで落ちているようなのだが、 とりあえずフラグを拾わないように修正しビルドし起動したら、動くようになった。 libpthreadの中で落ちているが、引数には問題なさそうなのでpthreadで利用 しているリソースの問題などが考えられるがとりあえずデバッグは後回し。(2009/09/01)
解決済み:libpthreadではなくlibthr使うと問題が発生しなくなりました。 FreeBSD 6.4環境のlibpthreadはM:Nスレッディングを実装したKSEスレッドでlibthr は1:1スレッディングの実装になるようです。KSEスレッドはFreeBSD 6.4が最終 サポートで7からはlibthrに置き換わるようです。 Makefile.defsのfreebsdの部分を以下のようにすると良いです。

ifeq ($(OS), freebsd)
#       LDFLAGS += -rdynamic -pthread
        LDFLAGS += -rdynamic -lthr
else

semsではコントロール(SIPなど)とメディア(RTP)を分離していて、それぞれに コア部分とプラグインが存在する4つの部分による構成になっている。 コントロール部分のプラグインは提供されるサービスになりアプリケーションとも 呼ぶようだ。

semsのプラグインはC++かPythonで作れる。C++のプラグインはsems.confの load_pluginsで指定された名前のsoファイルをsems.confのplugin_path で指定されたディレクトリからdlopenで読み出し、プラグインのソースの EXPORT_SESSION_FACTORYマクロなどで定義された構造体を解析してクラスを特定し 必要なメソッドを呼び出す。

プラグインに必要な設定はsems.confのplugin_config_pathで指定されたディレクトリ に用意する。

sipctrlはかなり限定的な仕組みのようだ。SIPのイベントは受けるとのだがREGISTな どは実装されていないようだ。スタンドアローンでビルドできるようにMakefile が用意されているが実際にはビルドできない。まだ作りかけなのかもしれない。 どのような用途を想定しているのか理解できない。。。今週中にはどうにかしたい。 (2009/09/03)

apps/examples/announce_authのアプリケーションが使えた。このプラグインは confファイルに指定したアカウントでSIP Proxyにconfで指定した アカウントにINVITEしてアナウンスを流す物だ。READMEには SPIT(Spam over Internet Telephony)に利用しないようにと断りがある。 (2009/09/04)

このプラグインはAnnounceAuthFactory/AnnounceAuthDialog/DialerThreadという 三つのクラスで構成されていて、AnnounceAuthFactoryがメインのクラスになる。 このクラスから定期的にコールを行う、DialerThreadクラスを作成している。 これにより相手が、応答した場合には、AnnounceAuthFactory::onInviteが 呼ばれその中でAnnounceAuthDialogを作り処理を行っている。AnnounceAuthFactory は起動し終了するまでインスタンスが一つでAnnounceAuthDialogは一つの通話に 一つのインスタンスが作られる。

announce_auth
サーバー構成

announce_auth
SIPシーケンス

sems.confの設定は以下

application = announce_auth
fork=no
load_plugins=wav;sipctrl;announce_auth
loglevel=2
media_ip=10.0.1.34
plugin_config_path=/usr/local/etc/sems/etc/
plugin_path=/usr/local/lib/sems/plug-in/
stderr=yes
use_default_signature=yes

etc/announce_auth.confの設定は以下(自宅のSERは塀の中なので認証かかってません)

announce_path=/usr/local/lib/sems/audio/
default_announce=default_en.wav
auth_realm=10.0.1.32
auth_user=999
dial_ruri=sip:9@10.0.1.32
dial_to=<sip:9@10.0.1.32>
dial_from=<sip:999@10.0.1.32>
dial_fromuri=sip:999@10.0.1.32

この例ではSIPの処理とwavからG.711変換してRTPで送る機能を使っている。

困った事にリファレンスに使っているXMeetingとACTのSIP Phoneでは正常に アナウンスが流れるが「亀の甲羅干し」ではNGであった。。。ちょっと確認して みたい。
解決済み:aqsnd.cのデバッグのために入れていたデバッグライトが問題でした。 さすがに再生のコールバックにデバッグライトを入れるのは無謀でした。

この時のSDPの内容は以下の通り

v=0
o=sems 1581877310 764399311 IN IP4 10.0.1.34
s=session
c=IN IP4 10.0.1.34
t=0 0
m=audio 1024 RTP/AVP 0 3 8 96 97
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:96 telephone-event/8000

上のSDPはG.711だけになるのだが、このCODECはwav.soプラグインに入っている。

announce_authを参考にして、電話をかけてメッセージを録音するアプリを作って みた。(record_auth.tgz)

application = record_auth
fork=no
load_plugins=wav;mp3;sipctrl;record_auth
loglevel=2
media_ip=10.0.1.34
plugin_config_path=/usr/local/etc/sems/etc/
plugin_path=/usr/local/lib/sems/plug-in/
stderr=yes
use_default_signature=yes
recode_path=/tmp
recode_file=recode_test.mp3
auth_realm=10.0.1.32
auth_user=999
dial_ruri=sip:9@10.0.1.32
dial_to=<sip:9@10.0.1.32>
dial_from=<sip:999@10.0.1.32>
dial_fromuri=sip:999@10.0.1.32

電話をかけて相手が話した事を/tmp/recode_test.mp3ファイルとして保存する。 これの例はapps/mp3のプラグインをLAME のソースで出来るライブラリを引っ付けて作成して使っている。

SEMSで分かりにくいのは、プラグインがそれ自体で完結しているのかそれとも 上下関係がある物なのかが直感的に分からない事かもしれません。 たとえばSIPサーバにレジストするapps/reg_agentはapps/registrar_client を呼んでいます。上記のようにapps/mp3のようなプラグインはコア部分から自動的に 呼ばれたりもします。wav.soがないとG.711すら話せなくなります。 フレームワークとして非常に柔軟なのだがその分説明が難しいのかもしれないです。

announce_authとreg_agentを引っ付けてをSIPサーバにレジストして、その番号に SIP Phoneから電話をかけるとメッセージを再生されるアプリを作ってみた。 何回か呼び出し音がなってから応答したいのだが、方法が分からないのですぐに アナウンスが流れます。 (announce_regist.tgz)

announce_auth

通常の電話のシーケンスは以下のような仕組みだ。

call.png

この「呼び出し」から「応答」までの間をearly sessionと呼ぶようだ。

SERとSEMSでハイアベイラビリティを実現するには以下のような構成が考えられる。

High Availability

announce_registをちょっといじってエコーテストのアプリを作ってみた。 echo.soを呼び出すようにしたかったができなかったので、ソースをそのまま 突っ込んである。 sems.confの設定は以下の通り。 (echo_regist.tgz)

load_plugins=wav;gsm;speex;ilbc;sipctrl;registrar_client;echo_regist;session_timer
application = echo_regist
fork=no
stderr=yes
media_ip=10.0.1.34
plugin_config_path=/usr/local/etc/sems/etc/
plugin_path=/usr/local/lib/sems/plug-in/
use_default_signature=yes

上のアプリを実行するとspeexの場合以下のエラーが出ている。 使用しているspeexは1.2rc1。

(4031) ERROR: [81ac600] receive (AmRtpAudio.cpp:109): decode() returned 0
プラグインのタイプの一覧
定義時のマクロ 呼び出し時のメソッド 用途
EXPORT_SESSION_FACTORY getFactory4App() アプリケーションなど
EXPORT_SESSION_EVENT_HANDLER_FACTORY getFactory4Seh() セッション管理
EXPORT_PLUGIN_FACTORY
EXPORT_PLUGIN_CLASS_FACTORY
EXPORT_SIP_EVENT_HANDLER_FACTORY
getFactory4Di() いろいろ
EXPORT_LOG_FACILITY_FACTORY getFactory4LogFaclty() ログ関係

semsに直接関係ないが GalateaTalk という日本語音声合成ソフトを調べ始めてみた。プロジェクトとでは合成用の 辞書を作る部分と実際に合成する部分をつくっているようです。 モデルを作る部分はオープンソースの音声認識ソフトウエア Juliusを使用している模様です。 合成部分のソフトウエアのビルドは下記のような依存関係があります。

GalateaTalk

内部処理はChaSenとChaOneをforkしてパイプでつないで、これに文字列を食わ せて、読みを拾いだして音声合成をおこなうようになってるようだ。

ちょっと苦労してFreeBSDでGalateaTalkをつかってWAVファイルを作れるように してみた。問題になった点はconfigureが通らない、デバイス周りの処理が defineなどで外せない、UTF8対応になっていない。ChaSen,ChaOne,unidicを すべて何も設定せずにconfigureを通した結果、UTF8でないと解析できなくな った。素のGalateaTalkはEUCかSJISを前提にしているので、よくわからなかった のでGalateaTalkをUTF8化してみた。

semsのアナウンス機能とGalateaTalkを連携させる場合以下のような方法が 考えられる。

かなりいい加減なgtalkのFreeBSDでのパッチ (gtalk_freebsd.tgz)

大学機関での研究の成果としての実装のようなので、合成できる音声はあまり 聞き取りやすいとは言いがたい。あまり活発にはメンテナンスはされていない ようだ。

serをVer2にしてsemsのbinrpcctrlを試したが、いろいろ問題がありあきらめた。 問題点などは次のような感じだった。 気を取り直してちょっと調べてみようかと思う。

sems -> binrpcctrl.so -> ctl.so -> serという感じで流れるのだが、 両方のsoがデフォルトではFreeBSDでコンパイルが通らない。また無理矢理 通しても、設定の仕様が極めて分かりにくい。特にbinrpcctrlはおそらくlibbinrpcに IPV6を入れた時に混乱したような感じを受ける。あとそもそもbinrpcctrlはSer との接続には必要ないが自前のServerのコードを動かすとCoreを吐く。 最後に決定的なのがctl.soはBINRPC_VERSが1となっているが、binrpcctrl.soは BINRPC_PROTO_VERが0x2となっている。ソースはすべてあるので何らか動くように 出来るかもしれないが。。。どうもこの辺はぐしゃぐしゃになっている感じがあるが オープンソースの難しいところなのかもしれない。

SEMSはユーザの手元で動くコードではないのでGPLとしても修正部分の公開のしなく ても良いものと思われる。必要がなければ特定のバージョンの構成を理解して、そ のバージョンで作り込みを行うのが現実的なような気がしている。

SEMSは機能のわりにコードがすくなくその分、エラー処理や警告がかなり荒いよう な感じがある。警告もよくわからない場合も多く、また設定が間違っているだけで もCOREを吐く。

SERとSEMSの接続はfifo,unix domain socket(stream/datagram),IP socket (stream/datagram),sipを想定ているように設定ファイルでは見えるが、実際 にはすべてがサポートされているわけではなさそう。。。

1.0.1のbinrpcctrlを1.1.1のソースツリーにコピーしてビルドして試したがやはり うまくいかない。1.0.1と1.1.1のbinrpcctrlはソースファイルの構成が変わっている。

unixsockctrl.soを試してみたがこちらも駄目だ。。。1.2.0のソースを見ると どこにもbinrpcctrlの設定ファイルがないのでまったく動かなくなった可能性が 高いのかもしれない。。。まったくなにやっているのか。。。

serにはsercmdというコマンドがあり、これがctrl.soのテストコマンドとなるのだが 正常に動作するのでおそらく問題はsems側にある物と思われる。

binrpcctrlやunixsockctrlはいつのまにか動かなくなったから放置されているように しか見えないのだが。。。

binrpcctrlのソースはいったい何を考えて修正したか、また動作していて事があるの か極めて疑問である。そもそもbinrpcctrlのソースはbinrpcのバージョンを2でdefine しているがSERもKamailioも1のままである。またbinrpcctrlでは1バイト目の上位4 ビットのMAGICも0になっている。Bodyのデータの属性のtypeも定義が違っている。 送信も受信も同じデータ構造の対象系の通信でかつ同系列のコードにも関わらずまっ たく別実装というのも意味が分からない。1.0.1の時点でもこのような状態なのに 1.1.1にした時にソースの構成を変えている。これもままったく理解できない。

semsの0.10.0ではserとの接続用のコードは本体のAmServer.cppなどにあったようだ。 1.0.1ではこれをplug-in/unixsockctrlに移し新たにbinrpcの処理をplug-in/binrpcctrl に入れたようだ。1.2.1ではplug-in/unixsockctrlが無くなっている。

serの方は0.9.xでは本体のfifo_server.c(read)とmodules/tm(write)に通信用のコード が入っていたが2.xではmodules/ctlにfifo_server.cを移しbinrpcを追加したようだ。

おそらくunix socketからbinrpcへ移行を目指していたがこれがまともに動くようにな らなかった上に元々のunix socketも動かなくなったというのが真実ではないかと思わ れる。serとsemsは開発者が違って行き違いがあったのに、serが分裂した事で一段と 分けの分からない状態になっているように思われる。semsはとりあずplug-in/sipctrl が動いているのでNo problemなのかもしれない。オープンソースの難しさを露呈して いる。

Open JTalkをビルドするためにHTS-2.1.1_for_HTK-3.4.1をビルドを試したがいくつか エラーがでる。一つはX11のコードのHGraf.cがヘッダーが入っていないのでコンパイル できない。二つ目はesignal.cにあるARCHはFreeBSDにはない。あと最後にstrarr.cで includeしているmalloc.hはFreeBSDではstdlib.hに変更されている。

Open JTalkのビルドにはHTS本体は不要で、hts_engine だけあればOKだった。 ビルド方法などは ここが参考になった。

galateatalkとOpen JTalkは同じ研究者が開発したプロジェクトだが、内部のモデルが 違う事とgalateatalkはいろいろな機能を含んだフレームワーク的なプロジェクトだが、 Open JTalkはTTSのみの機能になっているようだ。

このページでOpen JTalkの事を書いているのは当然SEMSのモジュールとして使う事を 前提にしている。例えばSIPのInviteのヘッダーにGPS情報を付けて、近所の案内をTTS するモジュールなどが作れればと考えている。

announce_registをいじって、起動時のonLoadでOpen JTalkでwavファイルを作るよう にしてみた。ピッチが変だがとりあえず聞ける。sems.confのモジュールの設定は 以下のような感じ。 (open_jtalk.tgz)

load_plugins=wav;gsm;speex;ilbc;sipctrl;registrar_client;open_jtalk
application = open_jtalk

亀の甲羅干しで上のアナウンスを聞くと最初と最後がかなり切れてしまう。 SEMS側でインターバルを入れた方が良いのかもしれないがP123Sに比べても激しいので、 Linphoneかaqsnd.cに問題があるのかもしれない。

Open JTalkはテキストからWAVファイルの生成を行うように作られているため、 直接リアルタイムにSEMSで音声を送り出すにはかなりいじらなければならないと 思われる。Open JTalkはかなり高速だが、自宅の実行につかっているVAIO C1だと 合成に実時間以上かかるのでそもそも無理なのだが。

モジュールなどのビルド環境はAMD64上のFreeBSD 6.4のi386のjail環境を使っている。 VAIO C1では遅すぎて気が滅入る。

INVITEのヘッダーをいじろうかと考えたが、MESSAGEのボディーに情報をつっこんで semsに渡した方が良いように思えてきた。ところがLinphoneのapiにはMESSAGEの送信 がなさげ。。。受信は出来るみたいなんだが。。。MESSAGEは RFC3428 で定義されているようだ。

MESSAGEはeXosipにもサポートがないので、これも修正しなければ駄目みたい。 eXosip_call_build_message()をeXcall_api.cに追加して、linphonecore.cから呼ん で送るのが良さそう。


           |  F1 MESSAGE          |                         |
           |--------------------> |  F2 MESSAGE             |
           |                      | ----------------------->|
           |                      |                         |
           |                      |  F3 INVITE              |
           |                      | <-----------------------|
           |  F4 INVITE           |                         |
           |<-------------------- |                         |
           |                      |                         |
           |                      |                         |
           |                      |                         |
        Turtle                   SER                      SEMS

TurtleをいじってSEMSにMESSAGEを送り込んだところ以下のようなメッセージが 出る。

(8576) DEBUG: [81aca00] handleSipMsg (SipCtrlInterface.cpp:441): body = 
(8576) DEBUG: [81aca00] handleSipMsg (AmSipDispatcher.cpp:86): method: `MESSAGE'
 [7].
(8576) ERROR: [81aca00] onOoDRequest (AmApi.cpp:80): sorry, we don't support beg
inning a new session with a 'MESSAGE' message

プラグインで以下のメソッドをオーバライドすればMESSAGEイベントを拾える。

void AnnounceRegistFactory::onOoDRequest(const AmSipRequest& req)

MESSAGEメドッソをサポートするための eXosip2のパッチ

上のシーケンスでF2のMESSAGE受信からF3のINVITEを送信するコード。

void AnnounceRegistFactory::onOoDRequest(const AmSipRequest& req)
{
    string from_user = req.from_uri.substr(req.from_uri.find("sip:")+4);
    from_user = from_user.substr(0, from_user.find("@")); 
    string to = "sip:"+ from_user + "@" + ri.domain;
    string from = "sip:"+ ri.user + "@" + ri.domain;

    AmArg* a = new AmArg();
    a->setBorrowedPointer(new UACAuthCred("", ri.auth_user.c_str() , 
      ri.passwd.c_str()));
    AmUAC::dialout("msgcb", MOD_NAME, 
      to, "<" + from +  ">", from,
      "<" + to + ">", string(""),
      string("X-Extra: fancy\r\n"),
      a);

    return;
}
figsip
スポット天気予報のリクエストのMESSAGEのbodyの例
<?xml version="1.0"?>
<methodCall>
  <methodName>turtle.getPointWather</methodName>
  <params>
    <param>
        <value><point>40.206720776007 140.03280878134</point></value>
    </param>
  </params>
</methodCall>
カレンダー予約の例
<?xml version="1.0"?>
<methodCall>
  <methodName>turtle.setCalendarAlert</methodName>
  <params>
    <param>
        <value>
          <time>Sun Aug 19 14:24:06 +0000 2007</time>
          <msg>汐留で打ち合わせです</msg>
        </value>
    </param>
  </params>
</methodCall>
この例は、SIPのMESSAGEメソッドではなくWEBインターフェースで設定しても 良いかもしれない。




Copyright (C) 2009-2010 Hiroki Mori All Rights Reserved.