第１４回　Raspberry Pi で監視カメラを作ろう！　～カメラの遠隔操作(ONVIF)編～

Raspberry Piと専用のカメラモジュールを使用し、ONVIF対応の監視カメラを作成する[シリーズ記事](https://qiita.com/lis-hanzomon/items/cc1f7f316fbeb9579b28)です。
本記事は[リンク情報システム株式会社](https://www.lis.co.jp/)の有志が作成しています。

---

カメラから動画や静止画を取得できるようになったので、監視カメラとして機器間の接続性を高めるために、業界標準のI/Fに合わせるために、ONVIFのプロトコルをサポートしてみたいと思います。

#カメラの遠隔操作 (ONVIF)
###はじめに
ONVIFについて、wikiでは以下のように説明されてます。

> ONVIF（オーエヌブイアイエフ、Open Network Video Interface Forum）は、アクシス、ボッシュ、ソニーが立ち上げたネットワークカメラ製品のインターフェースの規格標準化フォーラムである。
> 
> ネットワークカメラの機能をWebサービスとして公開するのが特徴。クライアントはカメラからWSDLを取得してそのカメラが持つ機能の利用方法を動的に入手する。
> 
> カメラの動的な発見、カメラ情報の設定および取得、カメラの光学制御およびPTZ (Pan, Tilt, Zoom) 制御、イベントハンドリング、Video Analytics, ストリーミング、セキュリティといったネットワークカメラの利用に必要な一通りのインターフェイスを定義している。
> 参考元：[ONVIF - Wikipedia](https://ja.wikipedia.org/wiki/ONVIF)

簡単にまとめると、端末アプリから自動でカメラを検出。動画の再生やカメラ向きを切り替える事ができる。という事だと思います。

###ONVIFでの通信方法
ONVIFは、以下のプロトコルを駆使して通信を行います。

|機能|プロトコル|
|:--|:--|
|カメラ制御|SOAP|
|動画|RTSP や HTTP　※今回はRTSPを対象に作成|
|静止画|HTTP|

動画のRTSPや静止画のHTTPは、これまでに述べてきた事ですので、今回は、カメラ制御のSOAPの部分に関して実装を進めて行きます。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399637/c3facc78-779e-554b-7822-9d23f3ba91e7.png)

###gsoapを使用した ONVIF I/F
soap通信を実現するために、linux上で使用できるオープンソースのライブラリとしては、gSoapが存在しています。
gSoapを使用することで、ONVIFのサイトで公開されている WSDL ファイルより、プログラムのスケルトンを生成することができます。
ONVIFクライアントから、RaspberryPi上の監視カメラアプリに対して命令が実行されると、gSoapで生成したスケルトンの処理が実行されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399637/6f0319cb-1585-a9ce-414c-b4c6f8365554.png)

その為、このスケルトンに処理を実装していくことで、ONVIFクライアントの要求に応える事ができます。

まずは、以下のコマンドを実行して、WSDLよりスケルトンのヘッダファイル(onvif.h)を生成します

```sh
wsdl2h -c -o onvif.h http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl https://www.onvif.org/ver10/events/wsdl/event.wsdl https://www.onvif.org/ver10/recording.wsdl https://www.onvif.org/ver20/ptz/wsdl/ptz.wsdl
```

次に、ONVIFでは認証にWSSEを使用するため、生成された onvif.h にWSSE機能を追加するために、importの記述を追加します。

```cpp
#import "xop.h"    // xop = <http://www.w3.org/2004/08/xop/include>
#import "wsa5.h"   // wsa5 = <http://www.w3.org/2005/08/addressing>
#import "wsse.h"   // WSSEを追加
```

最後に以下のコマンドを実行して、スケルトンのソースを出力します。

```sh
soapcpp2 -I/usr/local/share/gsoap onvif.h
```

ここまでの手順で、以下のソースファイルが作成されます。
 ・soapC.c
 ・soapClient.c
 ・soapClientLib.c
 ・soapServer.c
 ・soapServerLib.c
 ・soapH.h
 ・soapStub.h

これらのソースは、ONVIFクライアントと接続・通信を行い、処理を要求されるまでのスケルトンになっています。
(※soapClient.c と soapClientLib.c はクライアント用のソースなので、使用しません。)

###処理の実装
次に、実装すべき関数は、以下のようにsoapStub.hに宣言されています。

```cpp
/******************************************************************************\
*                                                                            *
* Server-Side Operations                                                     *
*                                                                            *
\******************************************************************************/

    /** Web service operation 'SOAP_ENV__Fault' (returns SOAP_OK or error code) */
    SOAP_FMAC5 int SOAP_FMAC6 SOAP_ENV__Fault(struct soap*, char *faultcode, char *faultstring, char *faultactor, struct SOAP_ENV__Detail *detail, struct SOAP_ENV__Code *SOAP_ENV__Code, struct SOAP_ENV__Reason *SOAP_ENV__Reason, char *SOAP_ENV__Node, char *SOAP_ENV__Role, struct SOAP_ENV__Detail *SOAP_ENV__Detail);
    /** Web service operation '__tds__GetServices' (returns SOAP_OK or error code) */
    SOAP_FMAC5 int SOAP_FMAC6 __tds__GetServices(struct soap*, struct _tds__GetServices *tds__GetServices, struct _tds__GetServicesResponse *tds__GetServicesResponse);
    /** Web service operation '__tds__GetServiceCapabilities' (returns SOAP_OK or error code) */
    SOAP_FMAC5 int SOAP_FMAC6 __tds__GetServiceCapabilities(struct soap*, struct _tds__GetServiceCapabilities *tds__GetServiceCapabilities, struct _tds__GetServiceCapabilitiesResponse *tds__GetServiceCapabilitiesResponse);
    /** Web service operation '__tds__GetDeviceInformation' (returns SOAP_OK or error code) */
    SOAP_FMAC5 int SOAP_FMAC6 __tds__GetDeviceInformation(struct soap*, struct _tds__GetDeviceInformation *tds__GetDeviceInformation, struct _tds__GetDeviceInformationResponse *tds__GetDeviceInformationResponse);
                            ：
```
 

例えば、カメラの情報を取得するというI/F (devicemgmt.wsdl で定義されていた、GetDeviceInformation)　を実装した場合、以下のようになります。

```cpp
/** Web service operation '__tds__GetDeviceInformation' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 __tds__GetDeviceInformation(struct soap* soap, struct _tds__GetDeviceInformation *tds__GetDeviceInformation, struct _tds__GetDeviceInformationResponse *tds__GetDeviceInformationResponse)
{
    // 実際は、設定ファイルやROM等から情報を取得して通知する。
    tds__GetDeviceInformationResponse->Manufacturer    = soap_strdup(soap, "製造者名");
    tds__GetDeviceInformationResponse->Model           = soap_strdup(soap, "モデル名");
    tds__GetDeviceInformationResponse->FirmwareVersion = soap_strdup(soap, "バージョン番号");
    tds__GetDeviceInformationResponse->SerialNumber    = soap_strdup(soap, "シリアル番号");
    tds__GetDeviceInformationResponse->HardwareId      = soap_strdup(soap, "ハードウェアID");
    return 0;
}
```

実装すべき関数は沢山あるのですが、gSoapを使用することで、SOAP通信などの難しい部分の処理は、すべて自動で作成されるので、肝心の監視カメラの制御に専念するる事ができます。

###対応アプリとの通信
このようにONVIF対応として監視カメラを実装すれば、[今回自作するWindows用の監視カメラ映像再生用アプリ](https://qiita.com/lis-hanzomon/items/896342ec1af95d42db26)からのみではなく、一般に公開されているONVIF対応アプリから、カメラの映像が再生できるようになります。

例えば、GooglePlayにあるBiyee SciTech, Inc.の "ONVIF IP Camera Monitor (Onvifer)" で試したら、接続できる事が確認できました。
> ※2020/12/30追記　現状、iPhoneアプリでは動作確認がとれていません。ONVIF対応アプリと謳っている数点のiPhoneアプリで確認してみましたが、何れも映像の再生（通信）が出来ていません。

---
[インデックス記事へ](https://qiita.com/lis-hanzomon/items/cc1f7f316fbeb9579b28)
[第１３回記事へ](https://qiita.com/lis-hanzomon/items/5206a1934bb563366ca5)
[第１５回記事へ](https://qiita.com/lis-hanzomon/items/5435b10ad7f439749a5c)

---
[リンク情報システム株式会社](https://www.lis.co.jp)では一緒に働く仲間を[随時募集](https://www.lis.co.jp/employ_top)しています！
また、お仕事のご依頼、ビジネスパートナー様も募集しております。[お気軽にご連絡](https://www.lis.co.jp/inquiry)ください。