RTSPストリームのヘルスチェック

# はじめに

これまで提供しているサービスで対象とするIPカメラのヘルスチェックにはnmapでpingチェックしたりポートの開閉状態で実施していたが、新しく対応しようとしているとあるカメラがベンダー側のRTSPサーバーを経由して配信する(うちから見えるのはRTSPサーバーのみ)ので、従来の方法ではRTSPサーバーのヘルスチェックになってしまう。

そこでRTSPサーバーが対象カメラのストリームを配信しているか？でチェックした方が良いと考え、実現方法を調べたのでその内容と、採用しようとした方法で少しハマったのでその原因と対処方法をメモとして残す。

# 方法

以下の2つの候補を発見した。

* ffmpegの静止画生成可否で判断
* curlでDESCRIBEして成否で判断

```Text
DESCRIBEが何かについては、「RTSP DESCRIBE」あたりで検索してみて欲しい。
ここではこれで調べようと思った経緯だけあとで少しだけ触れる。
```

# ffmpegの静止画生成可否で判断

簡単である。

```Text
ffmpeg -y -rtsp_transport tcp -i "rtsp://IP/stream" -frames 1 snapshot.png
```

上記コマンドを実行して成功するかどうかで判断する方法である。
でもヘルスチェックで映像データまで受けてしまうのはイマイチ。

# curlでDESCRIBEして成否で判断

```Text
curl -v -X DESCRIBE "rtsp://IP/stream"
```

RTSPはビューア側から視聴をリクエストするとOPTIONS → DESCRIBE → SETUP ... とネゴシエーション的なことを実施して映像データを取得・再生開始となることは知っていた。

今回の発端も、この辺りをうまく活用できないかなーと思ったからでもあった。
目論見通りDESCRIBEで、しかもcurlで手軽に判断できること分かった。
めでたしめでたし、、、と思ったら違った。

```Text
curl -v -X DESCRIBE "rtsp://IP/stream"
*   Trying IP:NN...
* Connected to IP (IP) port NN (#0)
> OPTIONS * RTSP/1.0
> CSeq: 1
> User-Agent: curl/7.81.0
>
< RTSP/1.0 200 OK
< CSeq: 1
< Server: xxxxx
< Cache-Control: no-cache
< Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, RECORD, GET_PARAMETER
< Supported: play.basic, con.persistent
<
* Connection #0 to host IP left intact
```

DESCRIBEかと思ったらOPTIONSになっている。
それにstream部分に適当な文字(=配信されていないストリームとして)を入れても200 OKになる。
RTSPサーバーに何が使えるかを問い合わせるだけなので200 OKになるのかなと。

ではなぜ-XでDESCRIBEを指定しているにもかかわらずOPTIONSになるのか分からなかったが、以下のサイトも見つけていたのでlibcurlでも試してみた。

https://stackoverflow.com/questions/31506917/rtsp-describe-on-axis-camera-with-libcurl-v7-43-0-returns-rtsp-errorcode-400

感謝！！

# libcurl（C言語でのサンプル）

```
#include <curl/curl.h>

int main(void)
{
  CURL *curl = curl_easy_init();
  if(curl) {
    CURLcode res;
    curl_easy_setopt(curl, CURLOPT_URL, "rtsp://IP/stream");
    curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_RTSP);
    curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
  }
}
```

この時の実行確認はtsharkによるパケットキャプチャにて実施した。(ダンプ結果が16進と10進で、掲載するためには両方を編集していかないといけないが面倒なので省略する)

```
DESCRIBE * RTSP/1.0
```

こちらは無事にDESCRIBEになったがレスポンスは403 Forbiddenになってしまった。

VLCではrtsp://IP/streamの映像視聴が行えるのに何故だ？？？ということで、VLCでの視聴時もパケットキャプチャをしてみた。(DESCRIBEの部分だけを以下に抜粋)

```
DESCRIBE rtsp://IP/stream RTSP/1.0
CSeq: 3
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Accept: application/sdp
```

DESCRIBEの後ろが"*"か"rtsp://IP/stream"の違いが確認できた。
そこで指定方法を調べてみると、curl_easy_setopt()でCURLOPT_RTSP_STREAM_URIを指定するとよさげなことが分かった。さきほどのソースに

```
curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, "rtsp://IP/stream");
```

の行を追加し、

```
#include <curl/curl.h>

int main(void)
{
  CURL *curl = curl_easy_init();
  if(curl) {
    CURLcode res;
    curl_easy_setopt(curl, CURLOPT_URL, "rtsp://IP/stream");
    curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_RTSP);
    curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
    curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, "rtsp://IP/stream");
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
  }
}
```

とすることで無事200 OKとなり、またstream部分を適当な文字にしたり、配信中のストリームを停止すると404 Not Foundとなった。

# node-libcurl

最終的にはJavaScriptで実現したかったのでnode-libcurlで実現したのが以下のソースとなる。

```
const { Curl, CurlProtocol, CurlRtspRequest } = require('node-libcurl');

const curl = new Curl();

curl.setOpt('URL', 'rtsp://IP/stream');
curl.setOpt('PROTOCOLS', CurlProtocol.RTSP);
curl.setOpt('RTSP_REQUEST', CurlRtspRequest.Describe);
curl.setOpt('RTSP_STREAM_URI', "rtsp://IP/stream");
curl.setOpt('VERBOSE', 1);

curl.on('error', curl.close.bind(curl));
curl.perform();
```

# 追記(2025/2/20)

以下の記事を書いた。

https://qiita.com/SECUAL_masa/items/48ba4a6bf4344532bd7e