NanoPi NEOをSIP電話機にする 前編

NanoPi NEOをSIP電話機にする

NanoPiでなくてもこの手のワンボードコンピュータでやりたいことの一つが小型SIPクライアント。

用意したもの。
上の写真にあるUSB接続タイプのハンドセット。
しかし、このハンドセット実はSkypeなどのネット電話のハンドセットとしてWindows用として数百円で販売されていたものだがWindows PCに繋いでも全く使えなかった代物。USB接続を行うとデバイスが接続されたことは認識されるものの、液晶画面表示無し、全ボタン反応無しマイク・スピーカー反応無し、つまりウンともスンとも言わない文鎮。10〜5年ほど前に購入してから1度も使えていないのでNanoPiに繋いで動くか不明。

この記事のNanoPi NEOで使用しているOSはLinux nanopineo 4.10.3-sun8i #2 SMP Tue Mar 21 17:30:21 JST 2017 armv7l GNU/Linux、この記事作成前日の3月21時点で最新のソースでフルビルドしたMainline系のもの。armbianのサイトで配布されているのとはカーネルモジュールの選択が少し違うかもしれない。

USBハンドセットをOS起動済みのNanoPi NEOに接続する。(上の写真の状態)

以下、例によってコマンド実行は行頭が#ならsudo付きかスーバーユーザーで、$なら一般ユーザーでという意味。

$ dmesg
[61363.162468] usbcore: registered new interface driver snd-usb-audio
[61665.443319] usb 2-1: USB disconnect, device number 2
[61901.092898] usb 2-1: new full-speed USB device number 3 using ohci-platform
[61901.595047] usb 2-1: New USB device found, idVendor=04b4, idProduct=0307
[61901.595082] usb 2-1: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[61901.715947] hid-generic 0003:04B4:0307.0002: hiddev0,hidraw0: USB HID v1.10 Device [HID 04b4:0307] on usb-1c1d400.usb-1/input3

OS起動後にハンドセットを接続したのでdmesgで最後の部分を確認。USBに何か新しいデバイスを認識している。HIDということなので "Raw Access to USB and Bluetooth Human Interface Devices" ということになる。良さ気でやっかいなタイプのデバイスだ。dmesgのメッセージを信用するなら /dev/hiddev0か/dev/hidraw0でアクセスできる筈。/devを見るとhiddev0は居なくてhidraw0が居た。

$ cat /dev/hidraw0

この状態でUSBハンドセットのボタンをアレコレ押してみると押したタイミングでターミナルに意味不明の文字がババッと出てくればOK.
Ctrl+Cで終了。

音声周りを確認する。

$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 1: U0x4b40x307 [USB Device 0x4b4:0x307], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: U0x4b40x307 [USB Device 0x4b4:0x307], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

一応使えるっぽい気な様子。

PJSIP

SIPのソフトウエアは取り敢えずPJSIPを使用する。PJSIPのarmbian用パッケージは提供されていないようなので自分でビルドする。armbianで何か大きめなアプリをビルドする場合はpkg-configやlibtoolは最低限入れておく。PJSIPならlibpjproject-devを追加すると結構揃う筈。その他サウンド関係のライブラリを必要なだけ追加。libsndobj-dev, libgsm1-dev, libsamplerate0-dev, libspeex-dev, libesd0-dev, libsndfile1-devこのあたりを入れればいける筈。(むしろ要らないのがあるかも)
NanoPiで何かやろうと思ったらラズパイかオレンジパイでやっている人の記事をググると参考になる筈。ただし、PJSIPのビルドに必要なパッケージとしてlibcelt-devを挙げているページはNanoPiとarmbianの組み合わせではあまり参考にならないかも。

# apt install subversion
$ svn checkout http://svn.pjsip.org/repos/pjproject/trunk
$ cd trunk 
$ ./configure --help  ←指定可能なオプションを確認しておく
$ ./configure --disable-video --disable-libwebrtc ←NanoPi NEOでは要らんでしょ
$ ./make dep
$ ./make
$ cd ~/trunk/pjsip-apps/bin ←実行可能なファイルはここにある

試すだけなのでmake installはしていない。

先ずは出来たPJSIPのテストツールを実行。(実行ファイル名は環境による)

$ ./pjsystest-armv7l-unknown-linux-gnueabihf
中略
M E N U :
---------
0: Tests
  00: Run test wizard
  01: Device Test
  02: Play Tone
  03: Play WAV File1
  04: Play WAV File2
  05: Record Audio
  06: Latency Test
  07: AEC/AES Test

  09: Exit
1: Options
  10: View Devices
  11: View Settings

Enter the menu number: 

メニューの2桁の数字を入力してそれぞれテストを実行。
メインメニューの02を実行してハンドセットの耳に当てる方から「プーー プーー」という音が聞こえないとちょっと絶望。 03を実行してハンドセットの耳に当てる方から男性の声でthank you for・・・というメッセージが聞こえてこないとちょっと絶望。
いずれのテストもメニューで選択後に1を押してテスト開始1で終了、OKなら1を押すというような流れ。NanoPiでやると05のRecord Audioで"Master/sound Underflow, buf_cnt=1, will generate 1 frame"が大量に出る。つまりマイクで音を拾う方は問題アリ。実際マイクで拾った音は盛大なビーガー音とノイズが入り、喋った声は「ワレワレハ ウチュウジンダ」みたいな感じ。

PJSIPの通話アプリを動かしてみる。

$ ./pjsua-armv7l-unknown-linux-gnueabihf --help  ←指定可能なオプションを表示してみる
表示省略

$ ./pjsua-armv7l-unknown-linux-gnueabihf         ←オプション無しで実行してみる

中略

>>>>
Account list:
  [ 0] <sip:192 .168.0.64:5060>: does not register
       Online status: Online
 *[ 1] </sip:192><sip:192 .168.0.64:5060;transport=TCP>: does not register
       Online status: Online
Buddy list:
 -none-

+=============================================================================+
|       Call Commands:         |   Buddy, IM & Presence:  |     Account:      |
|                              |                          |                   |
|  m  Make new call            | +b  Add new buddy       .| +a  Add new accnt |
|  M  Make multiple calls      | -b  Delete buddy         | -a  Delete accnt. |
|  a  Answer call              |  i  Send IM              | !a  Modify accnt. |
|  h  Hangup call  (ha=all)    |  s  Subscribe presence   | rr  (Re-)register |
|  H  Hold call                |  u  Unsubscribe presence | ru  Unregister    |
|  v  re-inVite (release hold) |  t  ToGgle Online status |  >  Cycle next ac.|
|  U  send UPDATE              |  T  Set online status    |  < Cycle prev ac.|
| ],[ Select next/prev call    +--------------------------+-------------------+
|  x  Xfer call                |      Media Commands:     |  Status & Config: |
|  X  Xfer with Replaces       |                          |                   |
|  #  Send RFC 2833 DTMF       | cl  List ports           |  d  Dump status   |
|  *  Send DTMF with INFO      | cc  Connect port         | dd  Dump detailed |
| dq  Dump curr. call quality  | cd  Disconnect port      | dc  Dump config   |
|                              |  V  Adjust audio Volume  |  f  Save config   |
|  S  Send arbitrary REQUEST   | Cp  Codec priorities     |                   |
+-----------------------------------------------------------------------------+
|  q  QUIT   L  ReLoad   sleep MS   echo [0|1|txt]     n: detect NAT type     |
+=============================================================================+
You have 0 active call
>>> 
</sip:192>

メニューが表示されるので取り敢えずSIPサーバに接続してみる。アカウント関係は右上のAccountにある。+a [Enter]で自分の(持っている) SIPアカウントを指定してSIPサーバに接続する。
以下の実行例、SIPサーバがsip.example.comとする。NanoPiで使用しているIPアドレスは192.168.0.64とする。自分のSIPアカウントが4321でパスワードがsecretpasswordとする。情報が足りないところはテキトーで良い。

>>> +a  
Your SIP URL: (empty to cancel): sip:4321@192.168.0.64
URL of the registrar: (empty to cancel): sip:asterisk@sip.example.com
Auth Realm: (empty to cancel): *
Auth Username: (empty to cancel): 4321
Auth Password: (empty to cancel): secretpassword

URLの入力は「sip:」で始まること。なお、上の例では自分のSIP URLをアカウント@IPアドレスにしているが@の右側は正直なんでも良いっぽい。

SIPサーバにレジストが成功すると先ほどのメニュー表示の直前に表示されるアカウントリストに以下のように表示される。
Account list:
 *[ 0] sip:4321@192.168.0.64: 200/OK (expires=61)
       Online status: Online
Buddy list:
 -none-

200/OKが出ること。expiresの値は無視

PJSIPの設定

上で入力したアカウント情報だが、毎回このようにするのは面倒なので設定ファイルに入れてしまいたい。
このメニューの右下にSave configがあって設定ファイルを作成できる。メニューでは特に設定らしいことはできないが、引数付きでpjsua-armv7l-unknown-linux-gnueabihfを実行した場合はその値が入った状態で保存されるみたい。
fを押して設定ファイル名(任意)を指定。

設定ファイルはテキストファイルなのでテキストエディタで開く。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#
# Logging options:
#
--log-level 5
--app-log-level 4

#
# Network settings:
#
--local-port 5060

#
# Media settings:
#
--snd-auto-close 1
#using default --clock-rate 16000
#using default --quality 8
#using default --ec-tail 200
#using default --ilbc-mode 30
--rtp-port 4000

#
# User agent:
#
--max-calls 4

#
# Buddies:
#

#
# SIP extensions:
#
--use-timer 1

上は引数無しでpjsua-armv7l-unknown-linux-gnueabihfを実行してそのまま設定ファイルにしたもの。要するにこれをベースにすれば良い。で、見ればわかるが、pjsua-armv7l-unknown-linux-gnueabihfの引数を1行に1つ指定してるだけ。
SIPアカウントを追加してみる。

1
2
3
4
5
--id=sip:4321@192.168.0.64
--registrar=sip:asrterisk@sip.example.com
--realm=*
--username=4321
--password=secretpassword

次回からは --config-file=保存したファイル名 を指定して起動する。

$ ./pjsua-armv7l-unknown-linux-gnueabihf --config-file=test.cfg

上はtest.cfgという設定ファイルを指定した場合

発信

一応使えるようになった筈なので通話してみる。

発信はメニューの左上に表示されている「m Make new call」がそれ。
[m][Enter]を押すと発信メニューが表示される。

>>> m
(You currently have 0 calls)
Buddy list:
 -none-

Choices:
   0         For current dialog.
  -1         All 0 buddies in buddy list
  [1 - 0]    Select from buddy list
  URL        An URL
  <enter>   Empty input (or 'q') to cancel
Make call:

buddyとかは登録していないので普通に通話先を指定するので通話先のURLを入力する。
たとえば内線番号2000番にかけたいと思った時は、2000を指定したのではダメ。
自分のSIP URLがsip:4321@192.168.0.64でSIPサーバのURLがhoge@sip.example.comであれば、sip:2000@sip.example.comであることが想像つくと思う。
つまり直接sip:2000@sip.example.com[Enter]と入力する。"URL"は不要。

すぐに呼び出しが始まり発信先がオフフック(応答)すれば通話できる。

毎回sip:hogehogeを入力するのは面倒なのでbuddyに登録する。
buddyはメニューの中央上段。buddy登録は+b[Enter]

>>> +b
Enter buddy's URI: (empty to cancel): sip:2000@sip.example.com
New buddy 'sip:2000@sip.example.com' added at index 1
>>>

sip:2000@sip.example.com がbuddy番号1に登録された。
メニューを表示したときにメニュー直前のbuddyリストにも表示されている筈。
次からはメニューから発信の[m][enter]の後に[1][Enter]で内線番号2000に発信できる。

ただし、注意としてメニューで登録したBuddyは設定ファイルを出力しても入っていない(なんじゃそりゃ)。PJSIP起動の度にBuddyを登録するのは馬鹿らしいので設定ファイルをエディタで編集して登録してやる。

着信

次は着信。とりあえず発信してもらう。上の例だと内線4321にかけてもらう。
特に操作しなくてもハンドセットが鳴動する筈。
着信はメニューの左上の方にある「a Answer call」

>>> a
Answer with code (100-699) (empty to cancel): 200
>>>

つまり[a][Enter]のあとに200を入力して[Enter]、電話に出たいだけなのに面倒。

これで通話できる。相手から切断したなら特にすることはない。自分の側から切断したいときはメニューの「h Hangup call」、これは[h][Enter]。

ステータスの変更

着信できる状態を変更するならメニュー中央の「T Set online status」または「t ToGgle Online status」。

>>> T

Choices:
  1  Available
  2  Busy
  3  On the phone
  4  Idle
  5  Away
  6  Be right back
  7  Offline
Select status (empty to cancel):

普通は着信可のAvailableの1か着信不可のOfflineの7を選択する程度。それを簡易に行うために[t][Enter]で実行のたびにOnline = AvailableとOfflineを切り替える。なお、たとえばBusyのときに[t][Enter]を押しても効かない筈。

とりあえず、今のところNanoPi NEOでは通話の音声はビー、ブギャー、ピロロロが中心で短波ラジオの電波がひどい状態のような殆ど何を言ってるのかわからない声「のようなもの」が入る程度。何かが根本的におかしいのでそれを直さないと使いものにならない。
あと、CLIの操作だとPC必須(しかも操作がタルい)なのでNanoPiを電話機にする意味がない。できたらハンドセットのボタンで操作できるようにしたい。

2017年3月24日追記:
NanoPi NEOの処理能力だとエコーキャンセラーが有効だとマイクで集音した音を処理しきれずにビーブギャーになるっぽい。--ec-tail=0を指定してやれば普通の声になることがわかった。ただしエコーに注意。

関連記事: