跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

ESP32-CAM 实时视频流传输与查看方案

如何使用 ESP32-CAM 模块实现实时视频监控。首先讲解了 Arduino IDE 环境搭建及 ESP32 开发板支持安装。接着演示了通过内置 CameraWebServer 示例代码在内网浏览器查看视频流的方法。最后提供了自定义 TCP 传输方案,将视频数据发送至服务器端(Python),实现外网远程查看。内容涵盖硬件连接、代码烧录及网络配置。

星落发布于 2026/4/6更新于 2026/10/24143 浏览
ESP32-CAM 实时视频流传输与查看方案

ESP32-CAM 简介

ESP32-CAM 是小尺寸的摄像头模组,该模块可以作为最小系统独立工作,尺寸仅为 2740.54.5mm。可广泛应用于各种物联网场合,适用于家庭智能设备、工业无线控制、无线监控、QR 无线识别、无线定位系统信号以及其他物联网应用,是物联网应用的理想解决方案。

![ESP32-CAM 模组外观]

产品特性:

  • 采用低功耗双核 32 位 CPU,可作应用处理器
  • 主频高达 240MHz,运算能力高达 600 DMIPS
  • 内置 520 KB SRAM,外置 8MB PSRAM
  • 支持 UART/SPI/I2C/PWM/ADC/DAC 等接口
  • 支持 OV2640 和 OV7670 摄像头,内置闪光灯
  • 支持图片 WiFi 上传
  • 支持 TF 卡
  • 支持多种休眠模式
  • 内嵌 Lwip 和 FreeRTOS
  • 支持 STA/AP/STA+AP 工作模式
  • 支持 Smart Config/AirKiss 一键配网
  • 支持二次开发

ESP32-CAM 的接口引脚图如下所示:

![ESP32-CAM 接口引脚图]

Arduino IDE 环境配置

安装 Arduino IDE

下载官方网址:https://www.arduino.cc/en/software 下载符合自己操作系统版本的 IDE 并安装。

Arduino IDE 获取 ESP32 开发环境

由于 Arduino IDE 中本身没有 ESP32 的开发版,需要手动进行安装,安装方式如下:

在 Arduino IDE 中,找到 工具 > 开发板 > 开发板管理,搜索 ESP32 或者直接选择 ESP32 Wrover Module。

![Arduino 开发板管理界面]

打开 Arduino IDE,找到 文件 > 首选项,将 ESP32 的配置链接填入附加开发板管理网址中。

# 配置链接 https://dl.espressif.com/dl/package_esp32_index.json 

![Arduino 首选项设置]

内网视频实时查看

选择示例代码

选择 文件 > 示例 > ESP32 > Camera > CameraWebServer,进入示例代码界面。

![CameraWebServer 示例路径]

修改示例代码中的相关参数

  • 修改示例代码中的 WiFi 和密码的名称。
  • 修改示例代码中的摄像头类型为 CAMERA_MODEL_AI_THINKER。

![代码修改示例]

运行结果

上传成功后,按一下 ESP32-CAM 开发板上的 RST 按键,重新启动开发板。 选择 工具 > 串口监视器,查看串口中输出的 IP,并用浏览器打开 IP 即可实时查看视频画面。

![串口输出 IP]

![浏览器查看视频]

代码

#include "esp_camera.h"
#include <WiFi.h>

















 * ssid = ;
 * password = ;

;

{
  Serial.();
  Serial.();
  Serial.();
  
   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 = ;
  config.pixel_format = PIXFORMAT_JPEG;
  
  
  
   (()) {
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = ;
    config.fb_count = ;
  }  {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = ;
    config.fb_count = ;
  }


  (, INPUT_PULLUP);
  (, INPUT_PULLUP);


  
   err = (&config);
   (err != ESP_OK) {
    Serial.(, err);
    ;
  }

   * s = ();
  
   (s->id.PID == OV3660_PID) {
    s->(s, ); 
    s->(s, ); 
    s->(s, ); 
  }
  
  s->(s, FRAMESIZE_QVGA);


  s->(s, );
  s->(s, );


  WiFi.(ssid, password);
   (WiFi.() != WL_CONNECTED) {
    ();
    Serial.();
  }
  Serial.();
  Serial.();
  ();
  Serial.();
  Serial.(WiFi.());
  Serial.();
}

{
  
  ();
}
// WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality
// Ensure ESP32 Wrover Module or other board with PSRAM is selected
// Partial images will be transmitted if image exceeds buffer size
// Select camera model
// #define CAMERA_MODEL_WROVER_KIT // Has PSRAM
// #define CAMERA_MODEL_ESP_EYE // Has PSRAM
// #define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
// #define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM
// #define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
// #define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
// #define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM
#include "camera_pins.h"
const
char
"TP-LINK_1760"
const
char
"987654321"
void startCameraServer()
void setup()
begin
115200
setDebugOutput
true
println
camera_config_t
20000000
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
// for larger pre-allocated frame buffer.
if
psramFound
10
2
else
12
1
#if defined(CAMERA_MODEL_ESP_EYE)
pinMode
13
pinMode
14
#endif
// camera init
esp_err_t
esp_camera_init
if
printf
"Camera init failed with error 0x%x"
return
sensor_t
esp_camera_sensor_get
// initial sensors are flipped vertically and colors are a bit saturated
if
set_vflip
1
// flip it back
set_brightness
1
// up the brightness just a bit
set_saturation
-2
// lower the saturation
// drop down frame size for higher initial frame rate
set_framesize
#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
set_vflip
1
set_hmirror
1
#endif
begin
while
status
delay
500
print
"."
println
""
println
"WiFi connected"
startCameraServer
print
"Camera Ready! Use 'http://"
print
localIP
println
"' to connect"
void loop()
// put your main code here, to run repeatedly:
delay
10000

程序烧录

通过配套下载器进行下载

使用配套的下载器连接 ESP32-CAM,点击左上角的编译验证按钮进行编译,编译成功后点击旁边的上传按钮烧录到 ESP32-CAM 开发板中。

![编译与上传]

将下载器与 ESP32-CAM 安装到一起,使用数据线连接到电脑,安装商家提供的驱动,之后在 工具选项中选择对应的 开发板与串口。

![串口选择]

通过 USB 转 TTL(CH340) 下载器进行下载

USB 转 TTL 下载器仅仅是连接线上与配套送的下载器不同,其他下载步骤是一样的。

USB 转 TTL 下载器与 ESP32-CAM 的连接线如下:

  • USB 转 TTL VCC 接 ESP32-CAM 5V
  • USB 转 TTL GND 接 ESP32-CAM GND
  • USB 转 TTL RXD 接 ESP32-CAM TXD
  • USB 转 TTL TXD 接 ESP32-CAM RXD
  • 下载时,需要将 GPIO1 接到 GND 上,用来启动下载模式。

![USB 转 TTL 接线图]

外网视频实时查看

外网视频实时查看分为两部分:

  • ESP32-CAM 开发板中运行的程序;
  • 服务器中运行的程序。

通过 ESP32-CAM 将视频数据发送到服务器中,服务器运行接收程序进行接收并展示,这样的好处是可以发送到外部公网服务器中。

ESP32-CAM 程序

#include <Arduino.h>
#include <WiFi.h>
#include "esp_camera.h"
#include <vector>

const char* ssid = "TP-LINK_1760";
const char* password = "987654321";
const IPAddress serverIP(192, 168, 1, 104); // 欲访问的地址,即服务器的 ip,可内网也可公网
uint16_t serverPort = 18080; // 服务器端口号

#define maxcache 1430
WiFiClient client; // 声明一个客户端对象,用于与服务器进行连接

// CAMERA_MODEL_AI_THINKER 类型摄像头的引脚定义
#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

static camera_config_t camera_config = {
  .pin_pwdn = PWDN_GPIO_NUM,
  .pin_reset = RESET_GPIO_NUM,
  .pin_xclk = XCLK_GPIO_NUM,
  .pin_sscb_sda = SIOD_GPIO_NUM,
  .pin_sscb_scl = SIOC_GPIO_NUM,
  .pin_d7 = Y9_GPIO_NUM,
  .pin_d6 = Y8_GPIO_NUM,
  .pin_d5 = Y7_GPIO_NUM,
  .pin_d4 = Y6_GPIO_NUM,
  .pin_d3 = Y5_GPIO_NUM,
  .pin_d2 = Y4_GPIO_NUM,
  .pin_d1 = Y3_GPIO_NUM,
  .pin_d0 = Y2_GPIO_NUM,
  .pin_vsync = VSYNC_GPIO_NUM,
  .pin_href = HREF_GPIO_NUM,
  .pin_pclk = PCLK_GPIO_NUM,
  .xclk_freq_hz = 20000000,
  .ledc_timer = LEDC_TIMER_0,
  .ledc_channel = LEDC_CHANNEL_0,
  .pixel_format = PIXFORMAT_JPEG,
  .frame_size = FRAMESIZE_QVGA,
  .jpeg_quality = 24, // 图像质量(jpeg_quality) 可以是 0 到 63 之间的数字。数字越小意味着质量越高。
  .fb_count = 1,
};

void wifi_init() {
  WiFi.mode(WIFI_STA);
  WiFi.setSleep(false); // 关闭 STA 模式下 wifi 休眠,提高响应速度
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi Connected!");
  Serial.print("IP Address:");
  Serial.println(WiFi.localIP());
}

esp_err_t camera_init() {
  // initialize the camera
  esp_err_t err = esp_camera_init(&camera_config);
  if (err != ESP_OK) {
    Serial.println("Camera Init Failed");
    return err;
  }
  sensor_t * s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV2640_PID) {
    // s->set_vflip(s, 1); // flip it back
    // s->set_brightness(s, 1); // up the blightness just a bit
    // s->set_contrast(s, 1);
  }
  Serial.println("Camera Init OK!");
  return ESP_OK;
}

void setup() {
  Serial.begin(115200);
  wifi_init();
  camera_init();
}

void loop() {
  Serial.println("Try To Connect TCP Server!");
  if (client.connect(serverIP, serverPort)) { // 尝试访问目标地址
    Serial.println("Connect Tcp Server Success!");
    // 向服务器发送数据
    while (1) {
      camera_fb_t * fb = esp_camera_fb_get();
      uint8_t* temp = fb->buf; // 这个是为了保存一个地址,在摄像头数据发送完毕后需要返回,否则会出现板子发送一段时间后自动重启,不断重复
      if (!fb) {
        Serial.println("Camera Capture Failed");
      } else {
        // 先发送 Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送 1430 余数最后发送
        // 完毕后发送结束标志 Frame Over 表示一张图片发送完毕
        client.print("Frame Begin"); // 一张图片的起始标志
        // 将图片数据分段发送
        int leng = fb->len;
        int timess = leng / maxcache;
        int extra = leng % maxcache;
        for (int j = 0; j < timess; j++) {
          client.write(fb->buf, maxcache);
          for (int i = 0; i < maxcache; i++) {
            fb->buf++;
          }
        }
        client.write(fb->buf, extra);
        client.print("Frame Over"); // 一张图片的结束标志
        Serial.print("This Frame Length:");
        Serial.print(fb->len);
        Serial.println(".Succes To Send Image For TCP!");
        // return the frame buffer back to the driver for reuse
        fb->buf = temp; // 将当时保存的指针重新返还
        esp_camera_fb_return(fb); // 这一步在发送完毕后要执行
      }
      delay(20); // 短暂延时 增加数据传输可靠性
    }
  } else {
    Serial.println("Connect To Tcp Server Failed! After 10 Seconds Try Again!");
    client.stop(); // 关闭客户端
  }
  delay(10000);
}

服务器程序 (Python)

import socket
import threading
import time
import numpy as np
import cv2

begin_data = b'Frame Begin'
end_data = b'Frame Over'

# 接收数据
# ESP32 发送一张照片的流程
# 先发送 Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送 1430 余数最后发送
# 完毕后发送结束标志 Frame Over 表示一张图片发送完毕
# 1430 来自 ESP32cam 发送的一个包大小为 1430 接收到数据 data 格式为 b''

def handle_sock(sock, addr):
    temp_data = b''
    t1 = int(round(time.time() * 1000))
    while True:
        data = sock.recv(1430) # 如果这一帧数据包的开头是 b'Frame Begin' 则是一张图片的开始
        if data[0:len(begin_data)] == begin_data:
            # 将这一帧数据包的开始标志信息(b'Frame Begin')清除 因为他不属于图片数据
            data = data[len(begin_data):len(data)]
            # 判断这一帧数据流是不是最后一个帧 最后一针数据的结尾时 b'Frame Over'
            while data[-len(end_data):] != end_data:
                temp_data = temp_data + data # 不是结束的包 讲数据添加进 temp_data
                data = sock.recv(1430) # 继续接受数据 直到接受的数据包包含 b'Frame Over' 表示是这张图片的最后一针
            # 判断为最后一个包 将数据去除 结束标志信息 b'Frame Over'
            temp_data = temp_data + data[0:(len(data)-len(end_data))] # 将多余的(\r\nFrame Over)去掉 其他放入 temp_data
            # 显示图片
            receive_data = np.frombuffer(temp_data, dtype='uint8') # 将获取到的字符流数据转换成 1 维数组
            r_img = cv2.imdecode(receive_data, cv2.IMREAD_COLOR) # 将数组解码成图像
            t2 = int(round(time.time() * 1000))
            fps = 1000 // (t2 - t1)
            cv2.putText(r_img, "FPS" + str(fps), (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
            cv2.imshow('server_frame', r_img)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            t1 = t2
            print("接收到的数据包大小:" + str(len(temp_data))) # 显示该张照片数据大小
            temp_data = b'' # 清空数据 便于下一章照片使用

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 这里的 ip 与端口是运行该程序的服务器的 ip 与端口,需要与 arduino 中的一致
server.bind(('192.168.1.104', 18080))
server.listen(5)
CONNECTION_LIST = [] # 主线程循环接收客户端连接
while True:
    sock, addr = server.accept()
    CONNECTION_LIST.append(sock)
    print('Connect--{}'.format(addr)) # 连接成功后开一个线程用于处理客户端
    client_thread = threading.Thread(target=handle_sock, args=(sock, addr))
    client_thread.start()

目录

  1. ESP32-CAM 简介
  2. Arduino IDE 环境配置
  3. 安装 Arduino IDE
  4. Arduino IDE 获取 ESP32 开发环境
  5. 配置链接 https://dl.espressif.com/dl/packageesp32index.json
  6. 内网视频实时查看
  7. 选择示例代码
  8. 修改示例代码中的相关参数
  9. 运行结果
  10. 代码
  11. 程序烧录
  12. 通过配套下载器进行下载
  13. 通过 USB 转 TTL(CH340) 下载器进行下载
  14. 外网视频实时查看
  15. ESP32-CAM 程序
  16. 服务器程序 (Python)
  17. 接收数据
  18. ESP32 发送一张照片的流程
  19. 先发送 Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送 1430 余数最后发送
  20. 完毕后发送结束标志 Frame Over 表示一张图片发送完毕
  21. 1430 来自 ESP32cam 发送的一个包大小为 1430 接收到数据 data 格式为 b''

更多推荐文章

查看全部
  • 三菱 R 系列 PLC 远程 IO 机器人通信与触摸屏配方应用
  • Windows 系统下 Python 新一代包管理工具 uv 及 VSCode 配置
  • Android 11.0 Framework 底层原理与核心机制解析
  • OpenClaw 免费 AI 大模型配置方法
  • React Native 侧滑列表实战:SwipeableFlatList 使用详解
  • Ubuntu 24.04 安装 ToDesk 远程桌面及配置
  • Windows 本地部署 OpenClaw:搭建 AI 助理并打通微信飞书
  • Java ArrayList 集合详解与性能分析
  • AI 产品经理学习路线:从零基础入门到精通指南
  • Python 闭包(Closure)核心概念与实战应用
  • 基于优化理论的相位恢复算法
  • AI Agent 新范式:基于 FastGPT 与 MCP 协议构建工具增强型智能体
  • 基于STM32标准库的智能小车避障与循迹实战
  • Robot Lab 基于 Isaac Lab 的机器人强化学习实战指南
  • 算法实战:归并排序与数组逆序对详解
  • OpenClaw + 飞书搭建专属 AI 机器人教程
  • Mac 远程连接 Windows 配置指南
  • 前端开发必读:HTML5 Doctype 与浏览器渲染模式详解
  • GitHub Copilot 提升开发效率实战记录
  • C++ 模板进阶:非类型参数、特化与分离编译

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online