#include <esp_camera.h>
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>

const char* ssid     = "";
const char* password = "";

WebServer server(80);

// ==============================================
// 引脚定义
// ==============================================
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      5
#define SIOD_GPIO_NUM      1
#define SIOC_GPIO_NUM      2

#define Y9_GPIO_NUM        9
#define Y8_GPIO_NUM        4
#define Y7_GPIO_NUM        6
#define Y6_GPIO_NUM       15
#define Y5_GPIO_NUM       17
#define Y4_GPIO_NUM        8
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM       16

#define VSYNC_GPIO_NUM     3
#define HREF_GPIO_NUM     46
#define PCLK_GPIO_NUM      7

// PCA9557
#define PCA9557_ADDR        0x19
#define PCA9557_OUTPUT_PORT 0x01
#define PCA9557_CONFIG_PORT 0x03
#define DVP_PWDN_GPIO       (1 << 2)

// ==============================================
// PCA9557 初始化
// ==============================================
void pca9557_init() {
  Wire.beginTransmission(PCA9557_ADDR);
  Wire.write(PCA9557_OUTPUT_PORT);
  Wire.write(0x05);
  Wire.endTransmission();
  delay(10);

  Wire.beginTransmission(PCA9557_ADDR);
  Wire.write(PCA9557_CONFIG_PORT);
  Wire.write(0xF8);
  Wire.endTransmission();
  delay(10);
}

// ==============================================
// 打开摄像头电源
// ==============================================
void dvp_pwdn(uint8_t level) {
  uint8_t data;
  Wire.beginTransmission(PCA9557_ADDR);
  Wire.write(PCA9557_OUTPUT_PORT);
  Wire.endTransmission();
  Wire.requestFrom(PCA9557_ADDR, 1);
  data = Wire.read();
  
  if (level) data |= DVP_PWDN_GPIO;
  else data &= ~DVP_PWDN_GPIO;
  
  Wire.beginTransmission(PCA9557_ADDR);
  Wire.write(PCA9557_OUTPUT_PORT);
  Wire.write(data);
  Wire.endTransmission();
  delay(10);
}

// ==============================================
// 视频流(使用 sendContent 自动分块)
// ==============================================
void handleStream() {
  camera_fb_t *fb = NULL;
  uint8_t *jpg_buf = NULL;
  size_t jpg_len = 0;

  // 发送 HTTP 响应头
  server.sendHeader("Content-Type", "multipart/x-mixed-replace;boundary=frame");
  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
  server.send(200);

  while (true) {
    // 检查客户端是否断开
    if (!server.client().connected()) {
      break;
    }

    // 获取帧
    fb = esp_camera_fb_get();
    if (!fb) {
      delay(10);
      continue;
    }

    // 转换 RGB565 到 JPEG
    bool ok = frame2jpg(fb, 12, &jpg_buf, &jpg_len);
    
    if (ok && jpg_buf != NULL) {
      // 发送帧头
      String head = "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: " + String(jpg_len) + "\r\n\r\n";
      server.sendContent(head);
      
      // 发送 JPEG 数据
      server.sendContent_P((const char*)jpg_buf, jpg_len);
      
      // 发送帧尾
      server.sendContent("\r\n");
      
      // 释放 JPEG 缓冲
      free(jpg_buf);
      jpg_buf = NULL;
    }

    // 释放摄像头帧
    esp_camera_fb_return(fb);
  }
}

// ==============================================
// 主页
// ==============================================
void handleRoot() {
  String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>ESP32-S3 摄像头</title></head>";
  html += "<body style='text-align:center; margin-top:20px;'>";
  html += "<h1>立创 ESP32-S3 GC0308 摄像头</h1>";
  html += "<img src='/stream' width='320' style='border:2px solid #333; border-radius:5px;'>";
  html += "</body></html>";
  server.send(200, "text/html", html);
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("\n--- 启动视频流系统 ---");

  // 1. I2C
  Wire.begin(SIOD_GPIO_NUM, SIOC_GPIO_NUM);
  delay(100);

  // 2. PCA9557
  pca9557_init();

  // 3. 打开电源
  dvp_pwdn(0);
  delay(300);

  // 4. 摄像头配置
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_1;
  config.ledc_timer   = LEDC_TIMER_1;
  config.pin_pwdn     = PWDN_GPIO_NUM;
  config.pin_reset    = RESET_GPIO_NUM;
  config.pin_xclk     = XCLK_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_d7       = Y9_GPIO_NUM;
  config.pin_d6       = Y8_GPIO_NUM;
  config.pin_d5       = Y7_GPIO_NUM;
  config.pin_d4       = Y6_GPIO_NUM;
  config.pin_d3       = Y5_GPIO_NUM;
  config.pin_d2       = Y4_GPIO_NUM;
  config.pin_d1       = Y3_GPIO_NUM;
  config.pin_d0       = Y2_GPIO_NUM;
  config.pin_vsync    = VSYNC_GPIO_NUM;
  config.pin_href     = HREF_GPIO_NUM;
  config.pin_pclk     = PCLK_GPIO_NUM;

  config.xclk_freq_hz = 16000000;
  config.pixel_format = PIXFORMAT_RGB565;
  config.frame_size   = FRAMESIZE_QVGA;
  config.jpeg_quality = 12;
  config.fb_count     = 3;
  config.fb_location  = CAMERA_FB_IN_PSRAM;
  config.grab_mode    = CAMERA_GRAB_LATEST;

  // 5. 初始化摄像头
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera Fail: 0x%x\n", err);
    return;
  }

  // 6. 镜像
  sensor_t *s = esp_camera_sensor_get();
  if (s->id.PID == GC0308_PID) {
    s->set_hmirror(s, 1);
  }

  Serial.println("[OK] 摄像头初始化成功");

  // 7. WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\n[OK] WiFi 连接成功");
  Serial.print("[OK] 访问地址:http://");
  Serial.println(WiFi.localIP());

  // 8. 网页路由
  server.on("/", handleRoot);
  server.on("/stream", handleStream);
  server.begin();
  
  Serial.println("[OK] Web 服务器已启动");
  Serial.println("==================================");
  Serial.println("视频流系统已就绪!");
  Serial.println("==================================");
}

void loop() {
  server.handleClient();
}

更多推荐