atomcam_toolsのHomeKit, RTMP対応

## はじめに

この記事はATOMTechのAtomCamをhackしているatomcam_toolsの内部処理のうち各種配信の実装について記述したものです。

atomcam_toolsに関しては下記を参照してください。

https://github.com/mnakada/atomcam_tools

https://qiita.com/mnakada/items/7d0fbcb6e629e1ddbd0c

https://qiita.com/mnakada/items/5da19a302b0f7521f225

https://qiita.com/mnakada/items/eddbf8b6f0095e279095

kitazakiさんのatomcam_toolsお試し記事も貼っておきます。

https://qiita.com/kitazaki/items/85a506b9be8718c5b3c3

https://qiita.com/kitazaki/items/8749ad6c0b95ac14b584



## atomcam_toolsのストリーミング配信について

atomcam_toolsではカメラからの配信としてRTSP, Apple HomeKit, RTMP(YouTube live), WebRTC(LAN内限定)をサポートしています。

ATOMCam2では正式版でもRTSP配信が可能ですが他のSwing等のモデルでのRTSP対応やRTSP以外のプロトコルの対応などは全く無く、柔軟性に欠ける為今回は独自に追加しています。

現状のatomcam_toolsのvideo/audioの変換パスを下図に示します。

![atomcam_tools_avpath.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159374/fbaab008-7722-52c7-7604-70ded6b0f447.png)

この中で使っているgo2rtcは下図の構成で、これがあれば本来はv4l2とALSAを直接読み込めるので右半分をまとめて処理できてv4l2rtspserverも不要になるはずなのですが、ATOMCamの処理負荷の問題があり、一旦v4l2rtspserverを通しています。

![go2rtc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159374/af7eee8c-2620-9d98-0e04-6874e29a5df3.png)

(図は[https://github.com/AlexxIT/go2rtc](https://github.com/AlexxIT/go2rtc)から)

go2rtcでは上の図の真ん中のところのVideo/Audioのフォーマット変換をffmpegを呼び出して処理させる構成になっています。
inputとoutputの構成によってフォーマット変換のためにffmpegが複数起動されることがあり、ATOMCamでは負荷が大きすぎてシステムが落ちるケースが多々発生してしまいます。

このため、VideoはHW encoderの出力をそのまま変換せずに使用し、Audioの変換だけ独自実装でv4l2rtspserverに追加してgo2rtcの中ではVideo/Audioの変換処理が不要な構成にすることでffmpegを呼び出さないようにしています。



## ATOMCam2の正式版のRTSPの処理

最初にATOMCam2の正式版アプリのRTSPの動作を確認してみました。

Ghydraで覗いたところ、shared memory経由で/system/bin/rtspと/tmp/live555MediaServerにFullHD H264/PCM alawを渡して出しているようです。

ここを置き換えることも考えましたが、iCamera_app内部でサーバーからMediaServerをダウンロードして起動していたりとややこしいことをしているので深追いはやめました。



## atomcam_toolsのRTSP処理

atomcam_toolsではiCamera_app(ATOMCAMのカメラアプリ本体)にLD_PRELOADでライブラリ呼び出しに割り込んでデータを取得しています。

videoはlocal_sdk_video_set_encode_frame_callbackの呼び出しをhookしてcallback関数を置き換えています。

置き換えたcallback関数の中でMainAVC(1080p), MainHEVC(1080p), SubHEVC(360p)の3系統のvideoをv4l2 loopbackに投げたあと本来のcallback関数を呼んでいます。

audioも同様にlocal_sdk_audio_set_pcm_frame_callbackの呼び出しをhookしてcallback関数を置き換えて、置き換えたcallback関数の中でPCM(S16_LE/8K-mono)をALSA loopbackに投げて本来のcallback関数を呼び出しています。

これらのvideo/audioデータをv4l2rtspserverでv4l2とALSAから読み出してRTSPとして配信しています。



## v4l2rtspserverの処理

v4l2rtspserverのaudioの入力部のALSACaptureでは、元々CPU endian (little endian)のPCMデータからNetwork endian (big endian)への変換処理が存在していました。

v4l2rtspserverの起動パラメータでAUDIOの入力ポートを指示するところがありますが、そこに出力するフォーマットを指定できるようにoptionを追加してALSACaptureのendian変換部分にフォーマット変換処理を追加しています。




今回配信プロトコルとして検討したのは下記になります。

| 対応プロトコル | Video                | Audio                                      |
| -------------- | :------------------- | ------------------------------------------ |
| RTSP           | H264,H265            | PCM,OPUS,AAC,etc                           |
| HomeKit        | H264                 | OPUS                                       |
| YouTube live   | H264,(H265?通らない) | AAC,MP3                                    |
| WebRTC         | H264                 | OPUS,G711 A-law, G711 u-law,(AAC?通らない) |

この中でHomeKitで必須なOPUSとYouTubeで必要なAAC or MP3のうちAACを選択して実装しました。

修正点は下記です。

- 引数のALSAのポートを設定するところに``@<format>``を追加することで出力フォーマットを指定できるように修正
- Audioの出力フォーマットを柔軟に設定できるように修正
- PCMデータ入力部分にOPUS encodeを追加
  - PCMは8K-sample/secのモノラルなので20ms周期で160sampleごとにencoderを呼び出してQueueに積んでいきます。
- PCMデータ入力部にAAC encodeを追加
  - OPUS同様ですが、fdk-aacの場合はAAC側でパケットサイズを決める必要があるようで、初期化時にaacEncInfo()でサイズを取得してPCMのパケットサイズを決定して、そのサイズ分のデータが揃うたびにencodeしてQueueに積んでいく動作になります。
- 各フォーマットごとのRTPSinkの呼び出しを追加
  - OPUSの場合はlive555のSimpleRTPSinkにSinglePacketで送信、AACの場合はMPEG4GenericRTPSinkでMPEG4カプセル化して送信になります。

詳細は下記を参照ください。

https://github.com/mnakada/v4l2rtspserver/tree/atomcam_tools



## go2rtcの処理

上記の最初の図に記述したようにRTSPを受けとって無変換でHomeKit, RTMP, WebRTCの出力に繋ぐようになっています。

go2rtcのconfigファイルにatomcam_toolsのWebUIからの設定値を反映するように変更しました。

また、go2rtcのHomeKitにpairing/un-pairingのAPIを追加してatomcam_toolsのWebUIから叩けるようにしています。



go2rtcは絶賛開発中のようです。最新のv1.9.3以降で大きく処理が変わったようで、v1.9.4でも不安定でRTMPとかも繋がらないのでv1.9.2にpatchを当てて使用しています。安定したらupdateしようかと思います。

修正点は下記の通りです。

- pairing/unpairing API追加
  - GET,DELETE /api/homekit/pairingを追加してpairing情報の取得とunpairingできるように修正
- 状態が変わった時にすぐにmDNSの更新がされるように修正
- HomeKitのQR-CodeでのpairingができるようにQR-CodeのURIを取得できるAPIの追加

詳細は下記を参照ください。

https://github.com/mnakada/go2rtc/tree/homekit-qrcode


## まとめと所感

ATOMCamをhackしているatomcam_toolsにHomeKit, RTMP, WebRTCの配信を追加しました。

HomeKitは自宅にAppleTVかHomePodがあれば外からでもiPhoneのホームアプリでATOMCamの映像が見えるので便利です。

RTMPはYouTube liveに直接配信できるので何か面白い使い方あるかもしれないです。

ただ、長時間使うとWiFiが切れたり、高負荷で再起動してしまう可能性もありYouTube liveが終了してしまうかもしれないです。

WebRTCはどう使っていいのかまだよく理解できていないです。とりあえずLAN内だけアクセスできるようにしています。

<br>

go2rtcを使って簡単に追加できると見込んでいたのでWebUIを作るくらいで終わると思ってましたが、ATOMCamのパフォーマンスに余裕がなく、負荷を下げる為に色々と想定と違うところに手を入れて泥沼にはまりました。

特にパスを一通り通すためにiCamera_app (MIPS asm:objdumpとGhydraで解析), libcallback.so (C), v4l2rtspserver (C++), go2rtc (golang), WebUI (javascript)の複数の言語を行ったり来たりしながら実装したため混乱してdebugに苦労しました。（単純に自分の能力の問題ですね）

また関連するRFCとAPI仕様も数多いので各プロトコルとフォーマットを勉強して理解するのも大変でした。

go2rtc, v4l2rtspserverの作者の方と、個別には挙げませんが色々とプロトコルやフォーマットの解説記事を書いてくださっている方々に感謝します。