M5StackとM5Camera でWiFi TCP/IP 動画ストリーミングする実験

M5StackとM5CameraをWiFi,TCP/IP,でMJPEG動画ストリーミングする実験 M5Stack

M5Stack(受信側)のスケッチ(プログラムソースコード)の入力

まず、動画ストリーミング受信側 M5Stack へのスケッチ(プログラムソースコード)を、Arduino IDE で入力していきます。

概要

大まかなプログラム構成は以下の感じにしました。
setup関数と、loop関数 は CPU core 1 タスクなので、合計3タスクです。

bool isWiFiConnected = false;
bool isPort80Handshake = false;

void setup() {
  TaskHandle_t taskClientCtrl_handl, taskClientStrm_handl;
  //CPU core 0 で port 80 制御コマンドコントロール用タスク生成
  xTaskCreatePinnedToCore(&taskClientControl, "taskClientControl", 4096, NULL, 5, &taskClientCtrl_handl, 0);
  //CPU core 0 で port 81 動画ストリーミング用タスク生成
  xTaskCreatePinnedToCore(&taskClientStream, "taskClientStream", 8192, NULL, 20, &taskClientStrm_handl, 0);
}

void loop() {
  //メインループはCPU core 1
  //M5Stack LCD 表示関数
  //ボタン操作関数
}

void taskClientControl(void *pvParameters){
  connectToWiFi(); //WiFiアクセスポイント接続
  while(true){
    if(isWiFiConnected){
      //port 80 HTTP コネクション ハンドシェイク
      //M5Camera へHTTP GETリクエスト送信
      //HTTP レスポンス受信
      //ボタン操作で常時
    }
    delay(1);
  }
}

void taskClientStream(void *pvParameters){
  while(true){
    if(isPort80Handshake){
      //port 81 HTTP コネクション ハンドシェイク
      //M5Camera OV2640 へHTTP制御コマンドリクエスト
      //HTTP レスポンス受信
      while(true){
        //動画ストリーミング開始
        delay(1);
      }
    }
    delay(1);
  }
}

void connectToWiFi(){
  //WiFi アクセスポイント接続
  isWiFiConnected = true;
}

M5Stackのボタンを押したら、port 80番でM5Cameraとコネクション確立(ハンドシェイク)します。
それが終わったら、port 81番でM5Cameraとコネクション確立(ハンドシェイク)し、port 81番で動画ストリーミング無限ループに入ります。

port 80番のtaskClientControlタスクと、port 81番のtaskClientStreamタスクは同じCPU core 0で動かしていますが、FreeRTOSのマルチタスクで動かしているので、独立して動作しています。
ただ、port 81番の動画ストリーミングはタスク優先度が高いので、port 80番タスクよりも優先されます。
それでも、ボタン操作してM5Cameraに制御コマンドを送れているので、ほぼ同時にタスクが動いている感じで、なかなか良いです。

スケッチ(プログラムソースコード)

では、実際のM5Stack(受信側)のスケッチ(プログラムソースコード)をArduino IDEに入力します。

M5StackのLCD表示は自前のライブラリを使います。
文字列表示は東雲フォントを使うので、12-14行にmicro SDHCカードに保存したフォントファイルのパスを指定します。

先ほどのテスト用プログラムでシリアルモニターに表示されたM5CameraのローカルIPアドレスを、19行目hostのところに入力して、書き換えます。

あと、注目するところは、readStringUntil 関数を最初のコネクション確立だけに抑えていて、動画ストリーミングのところでは使っていないところです。

(※SSIDやパスワードは、ESP32が第三者の手に渡った場合、ソフトウェアによって情報を抜き取られる場合があります。
このスケッチによるいかなるトラブルも当方では責任を負えませんのでご了承ください。)

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

/* This was modified by mgo-tec with the following esp32-camera library files:
 * The MIT License (MIT)
 * License URL: https://opensource.org/licenses/mit-license.php
 * Copyright (c) 2020 Mgo-tec. All rights reserved.
 * 
 * Use Arduino core for the ESP32 stable v1.0.4  
 */
#define MGO_TEC_BV1_M5STACK_SD_SKETCH
#include <mgo_tec_bv1_m5stack_sd_simple1.h> //ESP32_mgo_tec library. beta ver 1.0.71
#include <WiFi.h>

const char* utf8sjis_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく
const char* shino_half_font_file = "/font/shnm8x16.bdf"; //半角フォントファイル名を定義
const char* shino_full_font_file = "/font/shnmk16.bdf"; //全角東雲フォントファイル

const char* ssid = "xxxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください

const char* host = "192.168.0.22"; //M5Camera(相手先サーバー)のアドレス

const uint16_t disp_w_pix = 200;
const uint16_t disp_h_pix = 148;
const uint16_t line_max_bytes = disp_w_pix * 2;
const uint16_t bmp_data_size = disp_h_pix * line_max_bytes;
uint8_t pix_buf[bmp_data_size] = {};

enum SelCamCtrl {
  ping80 = 0,
  awb_off = 10, awb_on = 11,
  aec_auto = 20,aec_1 = 21, aec_2 = 22, aec_3 = 23, aec_4 = 24, aec_5 = 25,
  change_endian = 30,
  change_interval = 100,
  stop_stream = 200, start_stream = 201,
  reset_cam = 255,
} SelectCamCtrl = ping80;

enum StateStream{
  off_strm = 0, on_strm = 1, close_strm = 2
} StatusStream = off_strm;

enum DispSelAWB {
  AWB_off = 0, AWB_on = 1
} DispSelectAWB = AWB_off;

int8_t exposure_count = 0;
String aec_str[6] = {"Auto "," - 2 "," - 1 "," +-0 "," + 1 "," + 2 "};
String stream_str[3] = {"Stop ", "Start", "Intrv"};
String awb_str[2] = {" OFF ", " O N "};

const uint8_t bmp_head_size = 66;
uint32_t fps_timer = 0;
uint8_t fps_count = 0;
uint32_t ping80_lasttime = 0;
bool isWiFiConnected = false;
bool isPort80Handshake = false;
bool isPort81Handshake = false;
bool canSendCamCtrl = false;
bool canDisplayLCD = false;
//-------------------------
uint8_t offset = 1;
uint16_t btnA_x0 = 0, btnA_x1 = 107;
uint16_t btnB_x0 = 107, btnB_x1 = 212;
uint16_t btnC_x0 = 212, btnC_x1 = 319;
uint16_t btn_y0 = 197, btn_y1 = 233;

//********CPU core 1 task********************
void setup(){
  Serial.begin(115200);
  delay(1000);
  initDisplay();
  TaskHandle_t taskClientCtrl_handl, taskClientStrm_handl;
  xTaskCreatePinnedToCore(&taskClientControl, "taskClientControl", 4096, NULL, 5, &taskClientCtrl_handl, 0);
  xTaskCreatePinnedToCore(&taskClientStream, "taskClientStream", 8192, NULL, 20, &taskClientStrm_handl, 0);

  while(!isWiFiConnected){
    Serial.print('.');
    delay(500);
  }
  connectedWifiDisp();
  while(!isPort81Handshake){ //ESP32サーバーとのMJPEGハンドシェイクが終わるまで待つ
    actionButton();
    delay(1);
  }
}

void loop(){
  if(canDisplayLCD){
    LCD.drawPixel65kColRGB565Bytes(offset, offset, disp_w_pix, disp_h_pix, (uint8_t*)&pix_buf[0], bmp_data_size);
    canDisplayLCD = false;
  }
  if((StatusStream == on_strm) && (millis() - fps_timer > 1000)){
    Serial.printf("%d (fps)\r\n", fps_count);
    char fps_c[3];
    sprintf(fps_c, "%2d", fps_count);
    mM5.disp_fnt[4].dispText( mM5.font[4], String(fps_c) );
    fps_count = 0;
    fps_timer = millis();
  }
  actionButton(); //ボタン操作
}
//********CPU core 0 task********************
void taskClientControl(void *pvParameters){
  connectToWiFi();
  while(!isWiFiConnected){
    delay(1);
  }
  while(true){
    WiFiClient client80;
    connectClient80(client80);
    if (client80) {
      Serial.println("new client80");
      uint32_t time_out = millis();
      String get_request = "GET / HTTP/1.1\r\n";
      get_request += "Host: 192.168.0.22\r\n";
      get_request += "Connection: keep-alive\r\n\r\n";

      while (client80.connected()){
        client80.print(get_request);
        String req_str = "";
        while(true){
          if(client80.available()){
            req_str =client80.readStringUntil('\n');
            if(req_str.indexOf("200 OK") > 0){
              Serial.println(req_str);
              req_str = "";
              if(!receiveToBlankLine(client80)){
                delay(1); continue;
              }
              Serial.println("complete server responce!");
              Serial.println("Go stream!");
              isPort80Handshake = true;
              ping80_lasttime = millis();
              while(true){
                changeCamControl(client80);
                sendPing80(client80, 60000);
                while(client80.available()){
                  Serial.write(client80.read());
                  delay(1);
                }
                if(!isPort80Handshake) break;
                delay(1);
              }
              goto exit_1;
            }
          }
          delay(1);
        }
        delay(1);
      }
    }
exit_1:
    while(client80.available()){
      Serial.write(client80.read());
      delay(1);
    }
    Serial.println("!!!!!!!!!!!!!!!!!!!! client80.stop");
    delay(10);
    client80.stop();
    client80.flush();
    delay(10);
  }
}
//********CPU core 0 task********************
void taskClientStream(void *pvParameters){
  while(true){
    if(isPort80Handshake){
      WiFiClient client81;
      if(!connectStreamClient(client81)){
        canSendCamCtrl = true;
        Serial.println("Loop Out... wait client81 available");
        delay(10);
        client81.stop();
        client81.flush();
        Serial.println("!!!!!!!!!!!!!!!!!!!!! client81.stop");
        delay(10);
        SelectCamCtrl = ping80;
        isPort80Handshake = false;
        StatusStream = close_strm;
      }
    }
    delay(1);
  }
}
//********************************************
bool connectStreamClient(WiFiClient &client81){
  int stream_port = 81;
  while(true){
    if (!client81.connect(host, stream_port)) {
      Serial.print(',');
    }else{
      Serial.printf("client81.connected(2) %s\r\n", host);
      break;
    }
    delay(500);
  }

  String get_request = "GET /stream HTTP/1.1\r\n";
  get_request += "Host: 192.168.0.22:81\r\n";
  get_request += "Connection: keep-alive\r\n\r\n";

  client81.print(get_request);
  if(!receiveToBlankLine(client81)){
    Serial.println("connectStreamClient FALSE");
    return false;
  }
  Serial.println("stream header receive OK!");
  isPort81Handshake = true;
  StatusStream = on_strm;
  return receiveStream(client81);
}
//*********************************************
bool receiveStream(WiFiClient &client81){
  uint32_t time_out;
  while(StatusStream == on_strm){
    if(!receiveBoundary(client81)){
      delay(1); continue;
    }
    if(client81.available()){
      time_out = millis();
      char c[3];
      while(StatusStream == on_strm){
        if((char)client81.read() == '\n'){
          if((char)client81.read() == '\r'){
            if((char)client81.read() == '\n') break;
          }
        }
        if(millis() - time_out > 1000){
          receiveBoundary(client81);
          time_out = millis();
          delay(1);
        }
      }
      receiveStreamBMP(client81);
      while(StatusStream == on_strm){
        if(!canDisplayLCD) break;
        delay(1);
      }
    }
    delay(1);
  }
  return false;
}
//********************************************
bool receiveBoundary(WiFiClient &client81){
  char cstr[14] = {};
  while(StatusStream == on_strm){
    if(client81.available()){
      if((char)client81.read() == '-'){
        if(client81.read((uint8_t*)cstr, 13)){
          if(strcmp(cstr, "-myboundary\r\n") == 0){
            return true;
          }
        }else{
          delay(1); continue;
        }
      }else{
        delay(1); continue;
      }
    }
    delay(1);
  }
  return false;
}
//********************************************
bool receiveStreamBMP(WiFiClient &client81){
  if(!canDisplayLCD){
    if(isPort81Handshake){
      uint8_t recv_hed_buf[bmp_head_size];
      while(StatusStream == on_strm){
        if(client81.available()) {
          int tmp = 0;
          while(true){
            if(StatusStream != on_strm) return false;
            tmp = client81.read(recv_hed_buf, bmp_head_size);
            if(tmp < 0) {
              delay(1); continue;
            }
            while(tmp < bmp_head_size){
              int tmp2 = 0;
              if(StatusStream != on_strm) return false;
              tmp2 = client81.read(&recv_hed_buf[tmp], bmp_head_size - tmp);
              if(tmp2 < 0) {
                delay(1); continue;
              }else{
                tmp += tmp2;
              }
              delay(1);
            }
            break;
          }
          uint16_t ptr_addrs = 0;          
          uint16_t remain_bytes = bmp_data_size;
          tmp = 0;
          while(true){
            if(StatusStream != on_strm) return false;
            if(client81.available() > 0) {
              tmp = client81.read(&pix_buf[ptr_addrs], remain_bytes);
              if(tmp < 0){
                delay(1); continue;
              }
              ptr_addrs = ptr_addrs + tmp;
              remain_bytes = remain_bytes - tmp;
              if(remain_bytes <= 0) break;
            }
            delay(1);
          }
          while(true){
            if(StatusStream != on_strm) return false;
            if(client81.available()) {
              uint8_t cr[2] = {};
              if(client81.read(cr, 2) < 0){
                delay(1); continue;
              }
              if((char)cr[0] == '\r') { //0x0D = CR
                canDisplayLCD = true;
                fps_count++; //フレームレート表示カウントアップ
                return true;
              }else{
                canDisplayLCD = false;
                return false;
              }
            }
            delay(1);
          }
        }
        delay(1);
      }
    }
  }
  return false;
}
//********************************************
void connectClient80(WiFiClient &client80){
  int httpPort = 80;
  while(true){
    if(SelectCamCtrl == start_stream){
      if (client80.connect(host, httpPort)) {
        Serial.printf("client80.connected! %s\r\n", host);
        canDisplayLCD = false;
        break;
      }else{
        Serial.print(',');
      }
    }
    delay(1);
  }
}
//********************************************
void sendPing80(WiFiClient &client80, uint32_t interval_time){
  if((millis() - ping80_lasttime) > interval_time){
    sendCamCtrlRequest(client80, "ping80", "0");
    ping80_lasttime = millis();
  }
}
//********************************************
void changeCamControl(WiFiClient &client80){
  if(canSendCamCtrl){
    String id_str = "", val_str = "0";
    switch(SelectCamCtrl){
      case awb_off:
        id_str = "awb";
        break;
      case awb_on:
        id_str = "awb", val_str = "1";
        break;
      case aec_auto:
        id_str = "aec";
        break;
      case aec_1:
        id_str = "aec", val_str = "1";
        break;
      case aec_2:
        id_str = "aec", val_str = "2";
        break;
      case aec_3:
        id_str = "aec", val_str = "3";
        break;
      case aec_4:
        id_str = "aec", val_str = "4";
        break;
      case aec_5:
        id_str = "aec", val_str = "5";
        break;
      case change_endian:
        id_str = "change_endian";
        break;
      case change_interval:
        id_str = "change_interval";
        break;
      case stop_stream:
        id_str = "stop_stream";
        StatusStream = off_strm;
        isPort80Handshake = false;
        break;
      case start_stream:
        id_str = "start_stream";
        break;
      case reset_cam:
        id_str = "reset";
        break;
      default:
        id_str = "ping80";
        break;
    }
    sendCamCtrlRequest(client80, id_str, val_str);
    SelectCamCtrl = ping80;
  }
}
//********************************************
void sendCamCtrlRequest(WiFiClient &client80, String id_str, String val_str){
  String get_request = "GET /control?id=" + id_str;
  get_request += "&value=" + val_str;
  get_request += " HTTP/1.1\r\n";
  get_request += "Host: " + String(host);
  get_request += "\r\nConnection: keep-alive\r\n\r\n";
  client80.print(get_request);
  Serial.print(get_request);
  receiveToBlankLine(client80);
  canSendCamCtrl = false;
}
//*********************************************
bool receiveToBlankLine(WiFiClient &client){
  String req_str = "";
  uint32_t time_out = millis();
  while(StatusStream == on_strm){
    if(client.available()){
      req_str =client.readStringUntil('\n');
      Serial.println(req_str);
      if(req_str.indexOf("\r") == 0) return true;
    }
    if(millis() - time_out > 10000) {
      Serial.println("--------error Time OUT receiveToBlankLine");
      break;
    }
    delay(1);
  }
  return false;
}
//********************************************
void initDisplay(){
  mM5.init( utf8sjis_file, shino_half_font_file, shino_full_font_file );
  LCD.ILI9341init();
  LCD.brightness(255);  
  LCD.displayClear();
  LCD.drawRectangleLine(0, 0, disp_w_pix + offset, disp_h_pix + offset, 31, 63, 31);
  LCD.drawRectangleLine(btnA_x0, btn_y0, btnA_x1, btn_y1, "#ffffff");
  LCD.drawRectangleLine(btnB_x0, btn_y0, btnB_x1, btn_y1, "#ffffff");
  LCD.drawRectangleLine(btnC_x0, btn_y0, btnC_x1, btn_y1, "#ffffff");

  mM5.font[0].x0 = 5; mM5.font[0].y0 = 160;
  mM5.font[0].htmlColorCode( "#FFFFFF" );
  mM5.font[0].Xsize = 2, mM5.font[0].Ysize = 2;
  mM5.disp_fnt[0].dispText( mM5.font[0], "Stream" );
  mM5.font[0].x0 = 106; mM5.font[0].y0 = 160;
  mM5.font[0].htmlColorCode( "#FFFFFF" );
  mM5.font[0].Xsize = 1, mM5.font[0].Ysize = 2;
  mM5.disp_fnt[0].dispText( mM5.font[0], " Auto WhiteB" );
  mM5.font[0].x0 = 212; mM5.font[0].y0 = 160;
  mM5.font[0].htmlColorCode( "#FFFFFF" );
  mM5.font[0].Xsize = 2, mM5.font[0].Ysize = 2;
  mM5.disp_fnt[0].dispText( mM5.font[0], " 露出 " );

  mM5.font[1].x0 = 10; mM5.font[1].y0 = 200;
  mM5.font[1].htmlColorCode( "red" );
  mM5.font[1].Xsize = 2, mM5.font[1].Ysize = 2;
  mM5.disp_fnt[1].dispText( mM5.font[1], stream_str[0] );
  mM5.font[2].x0 = 120; mM5.font[2].y0 = 200;
  mM5.font[2].htmlColorCode( "blue" );
  mM5.font[2].Xsize = 2, mM5.font[2].Ysize = 2;
  mM5.disp_fnt[2].dispText( mM5.font[2], awb_str[0] );
  mM5.font[3].x0 = 230; mM5.font[3].y0 = 200;
  mM5.font[3].htmlColorCode( "#ff00ff" );
  mM5.font[3].Xsize = 2, mM5.font[3].Ysize = 2;
  mM5.disp_fnt[3].dispText( mM5.font[3], aec_str[0] );
}

void connectedWifiDisp(){
  mM5.font[0].x0 = 205; mM5.font[0].y0 = 0;
  mM5.font[0].htmlColorCode( "#FFFFFF" );
  mM5.font[0].Xsize = 1, mM5.font[0].Ysize = 2;
  mM5.disp_fnt[0].dispText( mM5.font[0], "Wi-Fi TCP OK!" );
  mM5.font[0].x0 = 205; mM5.font[0].y0 = 32;
  mM5.disp_fnt[0].dispText( mM5.font[0], "  MJPEG(BMP)" );
  mM5.font[0].x0 = 205; mM5.font[0].y0 = 64;
  mM5.disp_fnt[0].dispText( mM5.font[0], " ESP32-Camera" );

  mM5.font[4].x0 = 205; mM5.font[4].y0 = 96;
  mM5.font[4].htmlColorCode( "#00ff00" );
  mM5.font[4].Xsize = 2, mM5.font[4].Ysize = 2;
  String cam_fps_str = "    FPS";
  mM5.disp_fnt[4].dispText( mM5.font[4], cam_fps_str );
  mM5.font[4].x0 = 221;

  mM5.font[6].x0 = 5; mM5.font[6].y0 = 50;
  mM5.font[6].htmlColorCode( "#ff00ff" );
  mM5.font[6].Xsize = 2, mM5.font[6].Ysize = 2;
}
//********************************************
void connectToWiFi(){
  Serial.println("Connecting to WiFi network: " + String(ssid));
  WiFi.disconnect(true, true);
  delay(1000);
  WiFi.onEvent(WiFiEvent);
  WiFi.begin(ssid, password);
  Serial.println("Waiting for WIFI connection...");
}

void WiFiEvent(WiFiEvent_t event){
  switch(event) {
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.println("WiFi connected!");
      Serial.print("My IP address: ");
      Serial.println(WiFi.localIP());
      delay(1000);
      isWiFiConnected = true;
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("WiFi lost connection");
      isWiFiConnected = false;
      break;
    default:
      break;
  }
}
//********************************************
void changeAEC(){
  exposure_count++;
  if(exposure_count > 5) exposure_count = 0;
  if(exposure_count == 0) {
    SelectCamCtrl = aec_auto;
    mM5.font[3].htmlColorCode( "#ff00ff" );
    Serial.println("exposure=AUTO");
  }else{
    SelectCamCtrl = (SelCamCtrl)(exposure_count + 20);
    mM5.font[3].htmlColorCode( "yellow" );
    Serial.printf("exposure=%d\r\n", exposure_count);
  }
  mM5.disp_fnt[3].dispText( mM5.font[3], aec_str[exposure_count] );
  canSendCamCtrl = true;
}

void changeStateStream(){
  mM5.font[1].htmlBgColorCode( "black" );
  LCD.drawRectangleFill(btnA_x0+1, btn_y0+1, btnA_x1-1, btn_y1-1, "black");
  if(StatusStream == off_strm || StatusStream == close_strm){
    SelectCamCtrl = start_stream;
    StatusStream = on_strm;
    mM5.font[1].htmlColorCode( "green" );
    mM5.disp_fnt[1].dispText( mM5.font[1], stream_str[1] );
  }else if(StatusStream == on_strm){
    SelectCamCtrl = stop_stream;
    StatusStream = close_strm;
    mM5.font[1].htmlColorCode( "red" );
    mM5.disp_fnt[1].dispText( mM5.font[1], stream_str[0] );
  }
  canSendCamCtrl = true;
  Serial.printf("SelCamCtrl=%d\r\n", (uint8_t)SelectCamCtrl);
}

void actionButton(){
  mM5.btnA.buttonAction();
  switch( mM5.btnA.ButtonStatus ){
    case mM5.btnA.MomentPress:
      Serial.println("\r\nButton A Moment Press");
      changeStateStream();
      break;
    case mM5.btnA.ContPress:
      Serial.println("\r\n-------------Button A Cont Press");
      mM5.font[1].htmlColorCode( "white" );
      mM5.font[1].htmlBgColorCode( "blue" );
      LCD.drawRectangleFill(btnA_x0+1, btn_y0+1, btnA_x1-1, btn_y1-1, "blue");
      mM5.disp_fnt[1].dispText( mM5.font[1], stream_str[2] );
      SelectCamCtrl = change_interval;
      canSendCamCtrl = true;
      Serial.println("Send [Cam TCP Interval 0ms]");
      break;
    default:
      break;
  }

  mM5.btnB.buttonAction();
  switch( mM5.btnB.ButtonStatus ){
    case mM5.btnB.MomentPress:
      Serial.println("\r\nButton B Moment Press");
      if(DispSelectAWB == AWB_off){
        SelectCamCtrl = awb_on;
        DispSelectAWB = AWB_on;
        mM5.font[2].htmlColorCode( "white" );
      }else{
        SelectCamCtrl = awb_off;
        DispSelectAWB = AWB_off;
        mM5.font[2].htmlColorCode( "blue" );
      }
      canSendCamCtrl = true;
      Serial.printf("AWB=%d\r\n", (uint8_t)DispSelectAWB);
      mM5.disp_fnt[2].dispText( mM5.font[2], awb_str[(int)DispSelectAWB] );
      break;
    case mM5.btnB.ContPress:
      Serial.println("\r\n-------------Button B Cont Press");
      SelectCamCtrl = reset_cam;
      canSendCamCtrl = true;
      mM5.font[2].htmlColorCode( "white" );
      mM5.font[2].htmlBgColorCode( "red" );
      LCD.drawRectangleFill(btnB_x0+1, btn_y0+1, btnB_x1-1, btn_y1-1, "red");
      mM5.disp_fnt[2].dispText( mM5.font[2], "RESET" );
      Serial.println("Send [Reset]!!!");
      break;
    default:
      break;
  }

  mM5.btnC.buttonAction();
  switch( mM5.btnC.ButtonStatus ){
    case mM5.btnC.MomentPress:
      Serial.println("\r\nButton C Moment Press");
      changeAEC();
      break;
    case mM5.btnC.ContPress:
      Serial.println("\r\n-------------Button C Cont Press");
      SelectCamCtrl = change_endian;
      canSendCamCtrl = true;
      mM5.font[3].htmlColorCode( "#00ffff" );
      mM5.disp_fnt[3].dispText( mM5.font[3], "ENDIA" );
      Serial.println("Send [Change Endian]!!!");
      break;
    default:
      break;
  }
}

コンパイル書き込み実行

まず、Arduino IDE のボード設定は、送信側テスト用スケッチと同様で、以下の設定でOKです。

ボード:  ESP32 Dev Module
Upload Speed:  921600
CPU Frequency:  240MHz (WiFi/BT)
Flash Frequency:  80MHz
Flash Mode:  QIO
Flash Size:  4MB (32Mb)
Partition Scheme:  Default 4MB width spiffs (1.2MB APP/1.5MB SPIFFS)
Core Debug Level:  なし
PSRAM:  Disabled
シリアルポート:  ※ご自分の M5Camera のUSBポート
————————————-
書込装置: USBasp

 

では、事前にWiFiルーターを起動して置き、M5StackからUSBケーブルを外して、電源ボタンを2度押しして電源を切ります。
そして、先に紹介したフォントファイルを入れた micro SDHC カードを M5Stack に挿し込んで、再びパソコンとM5StackをUSB接続して、Arduino IDEでコンパイル書き込み実行します。

すると、WiFiアクセスポイントに接続完了すると、下図の様に表示されます。

先ほど送信用 M5Camera にテスト用 MJPEG (BMP)動画ストリーミングスケッチが書き込んであるので、左の「Stream」ボタンを押せば、テスト用動画がスタートするはずです。
冒頭で紹介した動画のように表示されればOKです。

先に紹介した通り、私のWiFi環境では、テスト用動画は12fps の速度が出ました。
悪くないです。

そして、これをパソコンやスマホブラウザに切り替えてみて下さい。
Windows10パソコンで、ブラウザがGoogle Chromeの場合、下図の様に表示されます。

ここで注意していただきたいのは、「Stop Stream」ボタンを押すと、ストリーミング無限ループを抜けて、HTTPコネクション切断します。
先に述べたように、一時停止処理は私には難解すぎて、諦めました。
TCPのタイムアウトがある為です。
よって、コネクション切断することにした訳です。
ですから、再び「Start Stream」ボタンを押しても反応しません。
再開する場合は、上部のページ更新ボタンを押して、「Start Stream」ボタンを押すという方式にしました。

ただ、その他のボタンは、動画ストリーミング中でも問題無く機能すると思います。
色の変更ボタンは、前回記事前々回記事と同様、線画と四角形移動の時のみ有効です。

では、次では送信側 M5Camera のイメージセンサ(OV2640)を使った動画ストリーミングスケッチ(プログラムソースコード)を紹介します。

コメント

タイトルとURLをコピーしました