ESP32-CAMで作る監視カメラ (RTSPストリーミング編)

ESP32-CAMで作る監視カメラ RTSPストリーミング編

ESP32-CAMを購入してダミーカメラに納めるという記事を公開した後、なぜかそれを使うところの記事をすっかり失念していました。
前回: ESP32-CAMで作るリビングカメラ (工作編)

ESP32-CAM DEVキットで作る監視カメラ 工作編 22 ダミーカメラにESP32-CAMを取り付けているところ。(写真は前回の工作編の使いまわし)
ESP32-CAM DEVキットで作る監視カメラ 工作編 25 完成したところ。(写真は前回の工作編の使いまわし)
AliExpressで販売されているESP32-CAMの1つ。このページに掲載されているESP32-CAMとは仕様が異なることがあります。

ESP32-CAMで作る監視カメラ RTSPストリーミング編 1
ESP32-CAMのプログラムのサンプルはArduino IDEにあるのですぐに使えます。
Arduino IDEのメニューバーから「ファイル」「スケッチ例」「ESP32」「Camera」「CameraWebServer」を辿ります。

ESP32CAMで作る監視カメラ RTSPストリーミング編 2
ボード(シングルボードコンピュータ)を選択します。
Arduino IDEのメニューバーから「ツール」「ボード(使用中のボードの名称)」「ESP32 Arduino」「AI Thinker ESP32-CAM」を辿ります。
上の画像では「ツール」の「シリアルポート」が灰色の無効状態になっていますが、ボードをPCにUSBケーブルで接続して通信できる状態になるとシリアルポートの設定ができるようになります。適切な通信速度を指定してください。

ESP32-CAMで作る監視カメラ RTSPストリーミング編 3
先に選択した「CameraWebServer」のソースの最初の方でボードの種類に応じてそのボード用の行を非コメント化します。
今回はESP32-CAMなので #define CAMERA_MODEL_AI_THINKER の行の行頭の「//」を削除します。
また、LANのWi-Fiに接続できるよう数行下の const char* ssid = "******" と const char* password = "******" の *****の部分にLANのWi-Fi APのSSIDとパスワードを入力します。
コンパイルして送信するとESP32-CAMが自動的に再起動して操作可能なウェブカメラサーバになります。LAN側でDHCPサーバが稼働していること。
Arduino IDEのメニューバーから「ツール」「シリアルモニタ」を辿りシリアルモニタを開きます。ESP32-CAMが起動してWi-Fiに接続するとシリアルモニタにIPアドレスが表示されます。ブラウザのURL欄にそのIPアドレスを入力してウェブカメラサーバを表示します。

ESP32-CAMで作る監視カメラ サンプルスケッチ画面
(前回の記事から画面を流用)
このサンプルスケッチは、高機能なのでいろいろ楽しめるものの監視カメラとして使うには不向きです。

Micro-RTSPを使ってRTSP対応監視カメラにする

RTSPストリームを実現するMicro-RTSP - https://github.com/geeksville/Micro-RTSPというライブラリがあります。
そのMicro-RTSPを使ったサンプルスケッチとしてESP32CAM_RTSP - https://github.com/Chihhao/ESP32CAM_RTSPが簡単で良さそうです。 GitHubからESP32CAM_RTSPのソースを取得し、ESP32CAM_RTSPディレクトリは丸ごとコピーしてArduino IDEのデータディレクトリの中に置きます。Arduino IDEのデータディレクトリはユーザーディレクトリの中のArduino (/home/ユーザー名/Arduino)など。環境によって違うかも。 Micro-RTSPはESP32CAM_RTSPに含まれているので別途入手する必要はありません。ESP32CAM_RTSPディレクトリのESP32CAM_RTSPディレクトリを丸ごとコピーしてArduino IDEのデータディレクトリの中のlibrariesディレクトリ下に置きます。

Arduino IDEを起動し、ESP32CAM_RTSPディレクトリの中の「ESP32CAM_RTSP.ino」を開きます。

ESP32CAM_RTSP.ino (編集)
  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//https://github.com/geeksville/Micro-RTSP

#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>
#include "OV2640.h"
#include "SimStreamer.h"
#include "OV2640Streamer.h"
#include "CRtspSession.h"

char *ssid = "****************";          // Put your SSID here
char *password = "****************";  // Put your PASSWORD here

WebServer server(80);
WiFiServer rtspServer(554);
OV2640 cam;
CStreamer *streamer;

//mjpeg串流
void handle_jpg_stream(void){
  WiFiClient client = server.client();
  String response = "HTTP/1.1 200 OK\r\n";
  response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
  server.sendContent(response);

  while (1)  {
    cam.run();
    if (!client.connected())
      break;
    response = "--frame\r\n";
    response += "Content-Type: image/jpeg\r\n\r\n";
    server.sendContent(response);

    client.write((char *)cam.getfb(), cam.getSize());
    server.sendContent("\r\n");
    if (!client.connected())
      break;
  }
}

//靜態影像
void handle_jpg(void){
  WiFiClient client = server.client();
  cam.run();
  if (!client.connected())
  {
    return;
  }
  String response = "HTTP/1.1 200 OK\r\n";
  response += "Content-disposition: inline; filename=capture.jpg\r\n";
  response += "Content-type: image/jpeg\r\n\r\n";
  server.sendContent(response);
  client.write((char *)cam.getfb(), cam.getSize());
}

//錯誤處理
void handleNotFound() {
  String message = "Server is running!\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  server.send(200, "text/plain", message);
}

//WiFi連線
void WifiConnect() {
  if(WiFi.status() == WL_CONNECTED) return;

  int t=0;
  WiFi.begin(ssid, password);
  while(true){
    if(WiFi.status() == WL_CONNECTED) break;
    delay(1000);
    t++;
    if(t>=5){
      t=0;
      WiFi.begin(ssid, password);
    }
  }
}

void setup(){
  //設定影像大小:UXGA(1600x1200),SXGA(1280x1024),XGA(1024x768),SVGA(800x600),VGA(640x480),CIF(400x296),QVGA(320x240),HQVGA(240x176),QQVGA(160x120)
  esp32cam_aithinker_config.frame_size = FRAMESIZE_XGA;
  esp32cam_aithinker_config.jpeg_quality = 10;
  cam.init(esp32cam_aithinker_config);

  //カメラ追加設定
  sensor_t * s = esp_camera_sensor_get();
  s->set_hmirror(s, 1); //左右反転 0無効 1有効
  s->set_vflip(s, 1); //上下反転 0無効 1有効
  //s->set_colorbar(s, 1); //カラーバー 0無効 1有効


  //開始WiFi連線
  WifiConnect();

  server.on("/", HTTP_GET, handle_jpg_stream);
  server.on("/jpg", HTTP_GET, handle_jpg);
  server.onNotFound(handleNotFound);
  server.begin();
  rtspServer.begin();
  streamer = new OV2640Streamer(cam);//啟動服務
}

void loop() {

  WifiConnect();

  server.handleClient();
  uint32_t msecPerFrame = 100;
  static uint32_t lastimage = millis();
  // If we have an active client connection, just service that until gone
  streamer->handleRequests(0); // we don't use a timeout here,
  // instead we send only if we have new enough frames
  uint32_t now = millis();
  if (streamer->anySessions()) {
    if (now > lastimage + msecPerFrame || now < lastimage) { // handle clock rollover
      streamer->streamImage(now);
      lastimage = now;

      // check if we are overrunning our max frame rate
      now = millis();
      if (now > lastimage + msecPerFrame) {
        printf("warning exceeding max frame rate of %d ms\n", now - lastimage);
      }
    }
  }
  WiFiClient rtspClient = rtspServer.accept();
  if (rtspClient) {
    streamer->addSession(rtspClient);
  }
}

シリアル通信の部分は運用時には不要なので取り除いています。代わりに画面反転とカラーバー表示を追加しています。(92-96行目)
Wi-FiのSSIDとパスワードを記入するだけで使用できます。Wi-FiのSSIDとパスワードを入力します。(11-12行目) カメラの映像が上下逆な場合は上下反転+左右反転も必要です。(94-95行目)。この反転については次へ。

カメラの映像は2通りで表示可能です。

  • http://IPアドレス/
  • rtsp://IPアドレス:554/mjpeg/1

前者は普通にネットワーク動画対応アプリやブラウザで表示可能ですが、後者は観ることができないかもしれません。また、両ストリームは同時再生できません。

ESP32CAMで作る監視カメラ RTSPストリーミング編 4
映像の上下が逆の場合、「回転」できる場合は180°回転させるだけです。
「回転」がない場合、「上下反転」を行うと一見目的達成のように見えますが、左右が逆になってしまいます。更に左右反転も行うと正常な上下逆になります。
なお、カメラ側の映像が上下逆さまであってもプレーヤー側で回転させて観ることができる場合があります。監視カメラ用のNVRアプリケーションでも映像の回転させて録画する機能を有することが多いでしょう。

Agent DVRへのカメラ登録

NVRのAgent DVRにRTSP対応のESP32-CAMを登録してみた。

ESP32CAMで作る監視カメラ RTSPストリーミング編 5
Agent DVRの設定画面。
「新しいデバイス」をクリックします。

ESP32CAMで作る監視カメラ RTSPストリーミング編 6
右上隅のドロップダウンは、「全般的」を選択状態(新しいデバイスの初期値)。 「有効」をオン。ソースの種類は「IPカメラ」を選択し、右端の「 … 」をクリックする。

ESP32CAMで作る監視カメラ RTSPストリーミング編 7
右上隅のドロップダウンは、「ビデオソース」を選択状態(初期値)。
「ライブURL」に、「 rtsp://IPアドレス:554/mjpeg/1 」を入力する。この画面ではこの1項目だけ。右下の「OK」をクリックする。
1つ戻った画面になる。

ESP32CAMで作る監視カメラ RTSPストリーミング編 8
(カメラの映像で動態検知して自動録画開始する場合のみ)
右上隅のドロップダウンから「検出器」を選択する。
機能有効化のスイッチを「On(オン)」にする。
中央の大きな「領域」の直下にある「検出器」のドロップダウンメニューで「単純」を選択する。
右下の「OK」をクリックする。

RTSP対応カメラ設定の肝はこれだけです。

とても簡単にESP32-CAMをNVRに接続可能な普通の監視カメラにできました。
ただし、ESP32-CAMが性能不足なのか少しラグります。ラグの面でいえばRTSPストリーミングよりHTTPストリーミングの方がややマシなようです。

関連記事: