サンプルの CameraWebServer.ino / web_cam.ino が難しかったので、ストリーミング(Motion JPEG)の部分だけ切り出した。
ピン配列はM5Stack Timer Cameraを使用する場合の設定。
メインスケッチ
#include <WiFi.h> #include <esp_camera.h> #include <esp_http_server.h> #include "html_text.h" // HTMLファイルの内容 const char *ssid = "******"; const char *password = "******"; #define PART_BOUNDARY "123456789000000000000987654321" static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; void setup() { Serial.begin(115200); // カメラ用パラメータ camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = 32; config.pin_d1 = 35; config.pin_d2 = 34; config.pin_d3 = 5; config.pin_d4 = 39; config.pin_d5 = 18; config.pin_d6 = 36; config.pin_d7 = 19; config.pin_xclk = 27; config.pin_pclk = 21; config.pin_vsync = 22; config.pin_href = 26; config.pin_sccb_sda = 25; config.pin_sccb_scl = 23; config.pin_pwdn = -1; config.pin_reset = 15; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.frame_size = FRAMESIZE_QVGA; // 320x240 config.jpeg_quality = 10; config.fb_count = 2; // CAMERA_GRAB_LATEST設定時は2以上必要 config.grab_mode = CAMERA_GRAB_LATEST; // 最後のフレームバッファだけを取得 // カメラ開始 esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } // WiFi開始 WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(WiFi.localIP()); // Webサーバー開始 start_MJPEG_server(); } void loop() {} // Webサーバー開始 void start_MJPEG_server(){ httpd_handle_t camera_httpd = NULL; httpd_handle_t stream_httpd = NULL; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); // ドキュメントルート用 httpd_uri_t index_uri = { .uri = "/", .method = HTTP_GET, .handler = index_handler, .user_ctx = NULL }; // MotionJPEG用 httpd_uri_t stream_uri = { .uri = "/stream", .method = HTTP_GET, .handler = stream_handler, .user_ctx = NULL }; // ドキュメントルート用 Serial.printf("Starting web server on port: '%d'\n", config.server_port); if (httpd_start(&camera_httpd, &config) == ESP_OK) { httpd_register_uri_handler(camera_httpd, &index_uri); } // MotionJPEG用 config.server_port += 1; config.ctrl_port += 1; Serial.printf("Starting stream server on port: '%d'\n", config.server_port); if (httpd_start(&stream_httpd, &config) == ESP_OK) { httpd_register_uri_handler(stream_httpd, &stream_uri); } } // ブラウザで、ドキュメントルートが表示された際の処理(HTMLを返す) static esp_err_t index_handler(httpd_req_t *req){ httpd_resp_set_type(req, "text/html"); httpd_resp_set_hdr(req, "Accept-Charset", "UTF-8"); return httpd_resp_send(req, HTML_TEXT.c_str(), HTML_TEXT.length()); } // ブラウザで、Motion JPEG(<img>)が表示された際の処理(閉じるまでループで撮影画像送信) static esp_err_t stream_handler(httpd_req_t *req){ camera_fb_t *fb = NULL; esp_err_t res = ESP_OK; char * part_buf[64]; // HTTPヘッダー送信(ストリーミング全体) res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); if(res != ESP_OK){ return res; } while(true){ // 撮影 fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); res = ESP_FAIL; } // HTTPヘッダー送信(jpeg1つ分) if(res == ESP_OK){ size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, fb->len); res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); } // jpegデータ送信 if(res == ESP_OK){ res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len); } // boundary(データ区切り文字列)送信 if(res == ESP_OK){ res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); } // フレームバッファ開放 if (fb) { esp_camera_fb_return(fb); } // 接続解除 もしくは エラー時に抜ける if(res != ESP_OK){ break; } } return res; }
表示用HTMLファイルのテキスト情報
HTMLだけ切り出して別ファイルに保管。
Arduino IDEでraw文字列(生文字列リテラル / Raw string literals)使えるの初めて知った。助かる。
static const String HTML_TEXT = R"( <!DOCTYPE html> <html> <head> <script> window.onload = function() { // imgのsrcに「http://192.168.x.x:81/stream」のurlを設定 var id_stream = document.getElementById("img_stream"); id_stream.src = document.location.origin + ":81/stream"; } </script> </head> <body> <img id="img_stream" width="320" height="240"> </body> </html> )";