You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32-Cam关闭摄像头流时崩溃问题求助

解决ESP32-Cam关闭浏览器后LoadProhibited崩溃问题

问题根源

关闭浏览器标签页后,TCP连接已断开,但代码中的connected标志仍为true,camLoop持续调用liveCam尝试向失效的WiFiClient实例写入数据,触发非法内存访问(LoadProhibited错误)。此外,直接赋值live_client = client会导致浅拷贝引用,原客户端对象销毁后live_client变为悬空引用,进一步加剧内存访问问题。

修复方案

1. 实时检测客户端连接状态

在liveCam函数开头添加连接检查,若客户端断开则重置状态并终止数据发送:

void liveCam(WiFiClient &client){
  // 先检查客户端是否仍连接
  if(!client.connected()){
    connected = false;
    client.stop();
    return;
  }
  // 原有的帧捕获和发送逻辑...
}

2. 处理新连接时关闭旧连接

当有新的/video请求到来时,先关闭之前可能存在的旧连接,避免资源冲突:

else if (req == "/video")
{
    // 关闭旧连接
    if(connected){
        live_client.stop();
        connected = false;
    }
    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;
}

3. 优化循环逻辑避免无效调用

在camLoop中,除了检查connected,也可以额外检查live_client.connected(),双重保障:

void camLoop() {
  http_resp();
  if(connected && live_client.connected()){
    liveCam(live_client);
  } else if(connected){
    // 客户端已断开但标志未更新,手动重置
    connected = false;
    live_client.stop();
  }
}

修改后的完整cam.ino代码

#include "esp_camera.h"

WiFiServer camServer(81);
bool connected = false;
WiFiClient live_client;

String 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>";

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_UXGA;
  config.jpeg_quality = 10;
  config.fb_count = 2;

  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){
  // 检查客户端连接状态
  if(!client.connected()){
    connected = false;
    client.stop();
    return;
  }
  //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 http_resp(){
  WiFiClient client = camServer.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")
      {
          // 关闭旧连接
          if(connected){
              live_client.stop();
              connected = false;
          }
          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 startCamera() {
  camServer.begin();
  configCamera();
}

void camLoop() {
  http_resp();
  if(connected && live_client.connected()){
    liveCam(live_client);
  } else if(connected){
    connected = false;
    live_client.stop();
  }
}

原理说明

  • 连接状态检测:每次发送帧前确认客户端连接有效性,避免向失效的TCP连接写入数据。
  • 旧连接清理:新连接到来时关闭旧连接,防止多个连接占用资源,同时避免悬空引用。
  • 双重状态校验:在主循环中额外校验连接状态,确保connected标志与实际连接状态同步。

内容的提问来源于stack exchange,提问作者PhoenixDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 02:23:08