ESP32-CAM 網路影像串流,初學者完整教學

把 ESP32-CAM 開發板連上你家的 Wi-Fi,然後用手機或電腦的瀏覽器,就能即時看到攝影機的畫面。不需要安裝任何 App,只要在同一個 Wi-Fi 底下,打開網頁就能看。

ESP32-CAM 網路影像串流,初學者完整教學

把 ESP32-CAM 開發板連上你家的 Wi-Fi,然後用手機或電腦的瀏覽器,就能即時看到攝影機的畫面。不需要安裝任何 App,只要在同一個 Wi-Fi 底下,打開網頁就能看。


一、你需要準備的東西

這篇範例只需要一片最常見的ESP32-CAM 開發板,AI-Thinker或相容板,已搭載 OV2640 攝影機模組。先不要用其它型號的ESP32內建鏡頭的開發板,那些開發板的腳位不同,傑森會另外介紹。

拿到ESP32-CAM時,鏡頭要自行裝上

上傳程式有兩種方式

ESP32-CAM 本身沒有 USB 接頭,所以上傳程式需要額外的工具。依你手上有什麼,選一種方式:

方式 A:使用上傳底板(推薦初學者)

上傳底板是一塊小小的轉接板,把 ESP32-CAM 直接插上去,再用 Micro USB 線接電腦就好。不需要任何接線,插上去就能用。

ESP32-CAM開發板 帶OV2640攝影模組 可選購上傳底板
ESP32-CAM開發板 帶OV2640攝影模組 本商品為相容版本,請注意哦! ※ 32位元CPU,可作應用處理器 ※ 主頻高達240MHz,運算能力高達 600 DMIPS ※ 內置 520 KB SRAM,外置4M PSRAM ※ 支持UART/SPI/I2C/PWM/ADC/DAC等接口 ※ 支持OV2640和OV7670攝像頭,內置閃光燈 ※ 支持圖片WiFi上傳 ※ 支持TF卡 ※ 支持多種休眠模式 ※ 內嵌Lwip和FreeRTOS ※ 支持 STA/AP/STA+AP 工作模式 ※

方式 B:使用 FTDI USB-TTL 轉接器

如果你沒有上傳底板,可以用 FTDI(或 CP2102、CH340 等 USB 轉 TTL 模組)來上傳。但需要自己接線,而且上傳前要手動把 GPIO0 接到 GND。

微雪 FT232RL USB轉TTL USB轉UART FT232模組
高檔的USB轉TTL模組! 採用FTDI原裝FT232RL晶片,穩定好用! MicroUSB接頭,超方便的! Pin腳眾多,不論用途是什麼,有這塊就夠了! 產品特性: ·採用FTDI原裝FT232RL晶片 ·支援Mac、Linux、Android、WinCE、Windows 7/8/8.1/10... ·支援3種供電模式:5V對外供電;3.3V對外供電;由外部供電(要求3.3V-5V) ·帶3個LED:TXD LED、RXD LED、POWER LED ·TXD、RXD、RTS#、CTS#:採

二、安裝 Arduino IDE 開發環境

Arduino IDE 設定

安裝開發板:如果之前已使用過ESP32開發板,就不用重複進行了。若是第一次,請參考我們另一篇專文教學哦!

在Arduino IDE中新增ESP32的開發板-以Nodemcu-32s為例
雖然Arduino Uno方便好用,如果真的需要WiFi功能,傑森一律建議直接換塊ESP32的板子,這是最直接的方法。 但Arduino IDE安裝好時並沒有ESP32開發板的選項,所以必須自行安裝,不過其實很簡單的。

步驟 4:選擇正確的開發板設定

在 Arduino IDE 的 工具 選單中,做以下設定:

設定項目 選擇
Board(開發板) AI Thinker ESP32-CAM
Partition Scheme Huge APP (3MB No OTA/1MB SPIFFS)
Upload Speed 115200(比較穩定,初學者建議用這個)
Port 選擇你的 COM Port(接上 USB 後會出現)
⚠️ Partition Scheme 一定要改成 Huge APP,否則程式太大會上傳失敗。

三、接線與上傳

方式 A:使用上傳底板

  1. 把 ESP32-CAM 插到上傳底板上(注意方向,有攝影機的那面朝外)
  2. 用 Micro USB 線接上電腦
  3. Arduino IDE 選好 Port
  4. 直接按「上傳」就好
如果上傳時卡在 Connecting...,試著按住底板上的 IO0 按鈕,再按一下 Reset,然後放開 IO0,這會強制進入下載模式。

方式 B:使用 FTDI USB-TTL

需要用杜邦線手動接線:

ESP32-CAM FTDI 模組
U0T(GPIO1) RX
U0R(GPIO3) TX
GND GND
5V VCC(5V)

重要:上傳程式前,要把 ESP32-CAM 的 GPIO0 用杜邦線接到 GND。 這是告訴 ESP32「我要進入下載模式」的訊號。

上傳步驟:

  1. 接好線,GPIO0 接 GND
  2. 在 Arduino IDE 點上傳
  3. 看到下方出現 Connecting... 時,按一下Reset
  4. 等待上傳完成
  5. 上傳完成後,把 GPIO0 從 GND 斷開
  6. 按一下 Reset,程式就會開始執行
⚠️ 如果上傳完忘記斷開 GPIO0,ESP32 會一直停在下載模式,程式不會執行。

四、程式碼

在 Arduino IDE 中建立新檔案,貼上以下程式碼。只需要修改前兩行的 Wi-Fi 名稱和密碼,其他不用動。

這支範例傑森已極度精簡,只有一項功能,就是可以在瀏覽器看到鏡頭拍到的串流影像。

#include "esp_camera.h"    // ESP32 攝影機函式庫
#include <WiFi.h>           // Wi-Fi 連線函式庫
#include "esp_http_server.h" // HTTP 伺服器函式庫

// ===== 請改成你自己的 Wi-Fi 名稱和密碼 =====
const char* ssid = "你的WiFi名稱";
const char* password = "你的WiFi密碼";

// ===== AI-Thinker ESP32-CAM 攝影機腳位(固定的,不用改)=====
#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

// ===== MJPEG 串流格式定義(HTTP 多段傳輸用,不用改)=====
#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

// 兩個伺服器:一個放網頁(port 80),一個放串流(port 81)
httpd_handle_t stream_httpd = NULL;
httpd_handle_t page_httpd = NULL;

// ========== 串流處理:不斷擷取畫面並傳給瀏覽器 ==========
static esp_err_t stream_handler(httpd_req_t *req) {
    camera_fb_t *fb = NULL;  // fb = frame buffer,存放一幀畫面
    char part_buf[64];

    // 設定回傳格式為 MJPEG 串流
    esp_err_t res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
    if (res != ESP_OK) return res;

    // 無限迴圈:持續拍照 → 傳送 → 拍照 → 傳送...
    while (true) {
        fb = esp_camera_fb_get();           // 從攝影機取得一幀 JPEG 圖片
        if (!fb) { res = ESP_FAIL; break; } // 拍照失敗就跳出

        // 把這幀圖片用 HTTP chunk 方式傳給瀏覽器
        size_t hlen = snprintf(part_buf, 64, _STREAM_PART, fb->len);
        res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
        if (res == ESP_OK) res = httpd_resp_send_chunk(req, part_buf, hlen);
        if (res == ESP_OK) res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
        esp_camera_fb_return(fb);           // 用完歸還 buffer

        if (res != ESP_OK) break;           // 傳送失敗(瀏覽器關閉)就停止
    }
    return res;
}

// ========== 首頁:顯示標題和嵌入串流畫面 ==========
static esp_err_t index_handler(httpd_req_t *req) {
    // 這段是完整的 HTML 網頁,會顯示在瀏覽器上
    const char html[] = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32-CAM 網路串流 - 傑森創工</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#111;color:#eee;font-family:Arial,sans-serif;text-align:center}
h1{font-size:20px;padding:8px 0 2px;color:#e94560}
.sub{font-size:12px;color:#888;padding-bottom:6px}
img{width:100%;max-width:640px;display:block;margin:0 auto;border-radius:6px}
</style>
</head>
<body>
<h1>ESP32-CAM 網路串流</h1>
<p class="sub">傑森創工</p>
<img id="stream" src="">
<script>
// 自動偵測 ESP32-CAM 的 IP,接上 port 81 的串流網址
var h=location.hostname;
document.getElementById('stream').src='http://'+h+':81/stream';
</script>
</body>
</html>
)rawliteral";
    httpd_resp_set_type(req, "text/html");
    return httpd_resp_send(req, html, strlen(html));
}

void setup() {
    Serial.begin(115200);

    // ===== 攝影機硬體設定(腳位對應,固定的)=====
    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_sccb_sda = SIOD_GPIO_NUM; config.pin_sccb_scl = SIOC_GPIO_NUM;
    config.pin_pwdn  = PWDN_GPIO_NUM;    config.pin_reset = RESET_GPIO_NUM;

    // ===== 攝影機參數設定(可以調整的部分)=====
    config.xclk_freq_hz = 10000000;       // 時鐘頻率 10MHz(降低可減少條紋)
    config.pixel_format = PIXFORMAT_JPEG;  // 輸出格式為 JPEG
    config.frame_size   = FRAMESIZE_QVGA;  // 解析度 320x240(小但流暢)
    config.jpeg_quality = 12;              // JPEG 品質 0~63,數字越小畫質越好
    config.fb_count     = psramFound() ? 2 : 1; // 有 PSRAM 用雙緩衝,串流更順

    // 啟動攝影機,失敗就停在這裡
    if (esp_camera_init(&config) != ESP_OK) {
        Serial.println("攝影機啟動失敗!請檢查排線");
        return;
    }
    Serial.println("攝影機啟動成功");

    // ===== 連接 Wi-Fi =====
    WiFi.begin(ssid, password);
    WiFi.setSleep(false);  // 關閉省電模式,串流更穩定
    Serial.print("正在連接 Wi-Fi");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nWi-Fi 已連線!");

    // ===== 啟動 Port 80 網頁伺服器(顯示有標題的控制頁面)=====
    httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
    cfg.server_port = 80;
    httpd_uri_t index_uri = { .uri = "/", .method = HTTP_GET, .handler = index_handler, .user_ctx = NULL };
    if (httpd_start(&page_httpd, &cfg) == ESP_OK) {
        httpd_register_uri_handler(page_httpd, &index_uri);
    }

    // ===== 啟動 Port 81 串流伺服器(提供 MJPEG 影像串流)=====
    cfg.server_port = 81;
    cfg.ctrl_port += 1;
    httpd_uri_t stream_uri = { .uri = "/stream", .method = HTTP_GET, .handler = stream_handler, .user_ctx = NULL };
    if (httpd_start(&stream_httpd, &cfg) == ESP_OK) {
        httpd_register_uri_handler(stream_httpd, &stream_uri);
    }

    // 印出網址,複製貼到瀏覽器就能看到畫面
    Serial.println("=========================================");
    Serial.printf("  開啟網頁:http://%s\n", WiFi.localIP().toString().c_str());
    Serial.printf("  純串流:  http://%s:81/stream\n", WiFi.localIP().toString().c_str());
    Serial.println("=========================================");
}

// loop 裡不需要做任何事,伺服器在背景自動運作
void loop() {
    delay(10000);
}

五、上傳程式並測試

  1. 把程式碼中的 Wi-Fi 名稱和密碼改成你自己的
  2. 按照第三節的方式接線並上傳
  3. 上傳成功後,開啟 Arduino IDE 的 Serial Monitor
  4. 右下角鮑率選 115200
  5. 按一下 ESP32-CAM 的 Reset 按鈕
  6. 你會看到類似這樣的訊息:
攝影機啟動成功
正在連接 Wi-Fi....
Wi-Fi 已連線!
=========================================
  開啟網頁:http://192.168.1.105
  純串流:  http://192.168.1.105:81/stream
=========================================
  1. 用手機或電腦的瀏覽器,輸入「開啟網頁」那行的網址
  2. 你就能看到有標題的即時影像串流頁面了!
⚠️ 手機和電腦必須跟 ESP32-CAM 連在同一個 Wi-Fi 下才能看到畫面。

六、常見問題排除

問題 解決方法
上傳時卡在 Connecting... 使用上傳底板:按住 IO0 → 按 Reset → 放開 IO0。使用 FTDI:確認 GPIO0 有接 GND,重試放 Reset 的時機
Serial Monitor 顯示亂碼 鮑率選錯了,改成 115200
攝影機啟動失敗 攝影機排線鬆了,打開卡扣重新壓好再扣回去
一直顯示 Connecting Wi-Fi Wi-Fi 名稱或密碼打錯了,注意大小寫
畫面有水平閃動條紋 通常是供電不足,換用 5V/2A 行動電源供電
反覆重啟 供電不足觸發 brownout 保護,換更穩定的電源
網頁打不開 確認手機/電腦和 ESP32-CAM 在同一個 Wi-Fi 下
上傳後程式沒有執行 使用 FTDI 的人:上傳完要把 GPIO0 從 GND 斷開,再按 Reset

七、想改解析度?

程式碼裡的 config.frame_size 可以改成其他值:

常數 解析度 說明
FRAMESIZE_QVGA 320×240 目前使用的,串流最流暢
FRAMESIZE_CIF 400×296 稍大一點
FRAMESIZE_VGA 640×480 畫質和速度平衡
FRAMESIZE_SVGA 800×600 畫質不錯但串流會變慢
FRAMESIZE_XGA 1024×768 適合拍照,串流會卡
FRAMESIZE_UXGA 1600×1200 最大解析度,只適合拍照

解析度越高,畫面更清楚但串流速度越慢,而且越吃電。初學者建議從 QVGA 或 VGA 開始。


八、原理簡單說明

這個範例做了三件事:

  1. 啟動攝影機 — 設定好腳位和參數,讓 OV2640 攝影機模組開始工作
  2. 連上 Wi-Fi — ESP32 連上你家的路由器,取得一個區域網路 IP
  3. 開兩個伺服器 — Port 80 提供有標題的網頁,Port 81 提供 MJPEG 影像串流。網頁裡用一個 <img> 標籤指向 Port 81 的串流網址,瀏覽器就會自動持續接收並顯示新畫面

所謂 MJPEG(Motion JPEG),就是不斷地拍一張 JPEG → 傳給瀏覽器 → 再拍一張 → 再傳,快速連續播放就變成「影片」了。跟真正的影片編碼(如 H.264)不同,MJPEG 不做幀間壓縮,所以比較吃頻寬,但好處是程式簡單、不需要額外的解碼器。

這個範例只是開始,但只要會用串流之後就不難囉!傑森還會上架其它相關教學,請大家期待吧^^

ESP32-CAM開發板 帶OV2640攝影模組 可選購上傳底板
ESP32-CAM開發板 帶OV2640攝影模組 本商品為相容版本,請注意哦! ※ 32位元CPU,可作應用處理器 ※ 主頻高達240MHz,運算能力高達 600 DMIPS ※ 內置 520 KB SRAM,外置4M PSRAM ※ 支持UART/SPI/I2C/PWM/ADC/DAC等接口 ※ 支持OV2640和OV7670攝像頭,內置閃光燈 ※ 支持圖片WiFi上傳 ※ 支持TF卡 ※ 支持多種休眠模式 ※ 內嵌Lwip和FreeRTOS ※ 支持 STA/AP/STA+AP 工作模式 ※