Demo 49: ESP32 HTTP Web server for camera live stream and bring it to the world

1. Introduction
In this demo, I will show you how to make a HTTP camera live stream application with ESP32 Cam and OV2640 camera. And publish it to the world so we can view it anywhere.
2. Hardware
I used the camera module:
Figure: ESP32 CAM with OV2640 cam
Board: ESP32 Wrover Module
Upload Speed: 115200
3. Software
3.1 Arduino code
In order to do live stream using HTTP we will use the below HTTP format:

HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame

--
frame  
Content-Type: image/jpeg

[image 1 encoded jpeg data]

--
frame
Content-Type: image/jpeg

[image 1 encoded jpeg data]

We have to re-use the Demo 12 to send this HTTP format. We have to process HTTP header and response manually.
The software flow of this demo:
- When web browser connect to web server, we send the index html.
- After loading the index html, web browser continue requesting /video
- When facing /video request the server will send the camera frame continuously.
The index html:
<meta charset="utf-8"/>
<style>
#content {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  min-height: 100vh;
}
</style>
<body bgcolor="#000000">
  <div id="content">
    <h2 style="color:#ffffff">HTTP ESP32 Cam live stream </h2>
    <img src="video">
  </div>
</body>
The full code:
#include "esp_camera.h"
#include <WiFi.h>

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

WiFiServer server(80);
bool connected = false;
WiFiClient live_client;


String index_html = "<meta charset=\"utf-8\"/>\n" \
                    "<style>\n" \
                    "#content {\n" \
                    "display: flex;\n" \
                    "flex-direction: column;\n" \
                    "justify-content: center;\n" \
                    "align-items: center;\n" \
                    "text-align: center;\n" \
                    "min-height: 100vh;}\n" \
                    "</style>\n" \
                    "<body bgcolor=\"#000000\"><div id=\"content\"><h2 style=\"color:#ffffff\">HTTP ESP32 Cam live stream </h2><img src=\"video\"></div></body>";

void configCamera(){
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  config.frame_size = FRAMESIZE_QVGA;
  config.jpeg_quality = 9;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
}

//continue sending camera frame
void liveCam(WiFiClient &client){
  //capture a frame
  camera_fb_t * fb = esp_camera_fb_get();
  if (!fb) {
      Serial.println("Frame buffer could not be acquired");
      return;
  }
  client.print("--frame\n");
  client.print("Content-Type: image/jpeg\n\n");
  client.flush();
  client.write(fb->buf, fb->len);
  client.flush();
  client.print("\n");
  //return the frame buffer back to be reused
  esp_camera_fb_return(fb);
}

void setup() {
  Serial.begin(115200);
  WiFi.begin("I3.41", "xxx");
  Serial.println("");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  String IP = WiFi.localIP().toString();
  Serial.println("IP address: " + IP);
  index_html.replace("server_ip", IP);
  server.begin();
  configCamera();
}
    
void http_resp(){
  WiFiClient client = server.available();                    
    /* check client is connected */           
  if (client.connected()) {     
      /* client send request? */     
      /* request end with '\r' -> this is HTTP protocol format */
      String req = "";
      while(client.available()){
        req += (char)client.read();
      }
      Serial.println("request " + req);
      /* First line of HTTP request is "GET / HTTP/1.1"  
        here "GET /" is a request to get the first page at root "/"
        "HTTP/1.1" is HTTP version 1.1
      */
      /* now we parse the request to see which page the client want */
      int addr_start = req.indexOf("GET") + strlen("GET");
      int addr_end = req.indexOf("HTTP", addr_start);
      if (addr_start == -1 || addr_end == -1) {
          Serial.println("Invalid request " + req);
          return;
      }
      req = req.substring(addr_start, addr_end);
      req.trim();
      Serial.println("Request: " + req);
      client.flush();
  
      String s;
      /* if request is "/" then client request the first page at root "/" -> we process this by return "Hello world"*/
      if (req == "/")
      {
          s = "HTTP/1.1 200 OK\n";
          s += "Content-Type: text/html\n\n";
          s += index_html;
          s += "\n";
          client.print(s);
          client.stop();
      }
      else if (req == "/video")
      {
          live_client = client;
          live_client.print("HTTP/1.1 200 OK\n");
          live_client.print("Content-Type: multipart/x-mixed-replace; boundary=frame\n\n");
          live_client.flush();
          connected = true;
      }
      else
      {
          /* if we can not find the page that client request then we return 404 File not found */
          s = "HTTP/1.1 404 Not Found\n\n";
          client.print(s);
          client.stop();
      }
    }       
}

void loop() {
  http_resp();
  if(connected == true){
    liveCam(live_client);
  }
}
3.2 Bring it to the world
In order to bring it to the world, we have to use ngrok
Steps to setup ngrok:
- Signup an account
- Copy your Authtoken here
- Download ngrok application here (I am using ubuntu, download it according to your OS)
- Unzip downloaded file, you will see ngrok application
- Open Terminal in the folder that contains ngrok app and run command:
"./ngrok authtoken your_copied_authtoken"
- Bring your esp32 web server to the world using command
"./ngrok http ip_of_esp32_webserver:80"
Figure: ngrok output
From the ngrok output, your public domain from ngrok will be: http://e059707c.ngrok.io
Open web browser and type the domain http://e059707c.ngrok.io
4. Result
The display is not really smooth.


Post a Comment

1 Comments

Dominick said…
The EPG will certainly be customizable by visitors, visit here that will certainly have the ability to develop their very own line-up of preferred networks and also web content. As full-screen layouts, EPGs will have a mini-overlay mode which will allow the viewer to keep watching the current program while browsing the schedule.