直接上代码:
一、实验概述
1.1 实验目标
实现ESP32-S3开发板采集摄像头图像数据(或本地测试数据),通过WiFi连接网络,基于RTMP协议将音视频流推送到指定的RTMP服务器(如Nginx-RTMP、阿里云视频直播等),并在客户端(如VLC、PotPlayer)成功拉流播放,验证推流功能的稳定性与可用性。
1.2 核心原理
ESP32-S3通过摄像头驱动采集图像数据,经JPEG编码压缩后,结合IDF框架中的网络组件(LWIP)建立TCP连接,通过RTMP客户端库封装音视频数据为RTMP数据包,最终推送到RTMP服务器。整个流程涉及硬件驱动、网络通信、协议封装三大核心模块的协同工作。
1.3 适用范围
本流程适用于搭载ESP32-S3芯片的开发板(如ESP32-S3-DevKitC-1),基于Espressif IDF 5.5.1开发环境,支持OV2640、OV7670等常见摄像头模块,可用于物联网视频监控、实时直播等场景的前期技术验证。
二、实验准备
2.1 硬件清单
|
硬件名称 |
规格参数 |
数量 |
用途说明 |
|---|---|---|---|
|
ESP32-S3开发板 |
至少4MB Flash,支持WiFi |
1块 |
核心控制单元,负责数据采集与推流 |
|
摄像头模块 |
OV2640(推荐)/OV7670 |
1个 |
采集图像数据,输出JPEG格式图像 |
|
USB数据线 |
Type-C(与开发板匹配) |
1根 |
供电、程序下载与串口调试 |
|
杜邦线 |
公对母/公对公 |
若干 |
连接开发板与摄像头模块 |
|
电脑 |
Windows10/11或Ubuntu20.04 |
1台 |
搭建开发环境、烧录程序与调试 |
2.2 软件与工具清单
|
软件/工具名称 |
版本要求 |
用途说明 | |
|---|---|---|---|
|
Espressif IDF |
5.5.1 |
ESP32-S3的官方开发框架,提供编译、烧录工具链 | |
|
VS Code |
最新稳定版 |
代码编辑,配合IDF插件提升开发效率 | |
|
ESP-IDF插件 |
适配IDF5.5.1 |
VS Code中集成IDF开发环境的插件 | |
|
RTMP服务器 |
Nginx-RTMP 1.2.1/阿里云直播 |
接收ESP32-S3推送的音视频流 | |
|
视频播放软件 |
VLC 3.0+/PotPlayer |
拉取RTMP服务器的流并播放,验证推流效果 | |
|
串口调试工具 |
SSCOM9.9/putty |
查看开发板输出的日志信息,排查问题 |

2.3 前置知识储备
-
熟悉ESP32-S3的基本硬件结构,了解GPIO、SPI等外设的使用
-
掌握IDF框架的基础使用,包括menuconfig配置、编译与烧录流程
-
了解RTMP协议的基本概念,知晓RTMP推流地址的组成格式(如rtmp://ip:port/app/stream)
-
具备基础的网络知识,能够配置开发板连接WiFi网络
三、实验环境搭建
3.1 IDF5.5.1开发环境搭建(Windows系统)

3.1.1 安装依赖工具
-
安装Python 3.8-3.11版本(推荐3.10),勾选“Add Python to PATH”选项,完成后在命令行输入
python --version验证安装成功。 -
安装Git,选择对应系统版本,安装过程中默认配置即可,通过
git --version验证。
3.1.2 下载并安装IDF5.5.1
-
打开浏览器访问ESP-IDF官方下载页面(https://docs.espressif.com/projects/esp-idf/zh_CN/v5.5.1/esp32s3/get-started/index.html),下载“ESP-IDF 工具安装器”(esp-idf-tools-setup.exe)。
-
运行安装器,勾选“ESP32-S3”芯片支持,选择安装路径(建议无中文无空格,如D:\ESP-IDF-5.5.1),点击“Next”。
-
勾选需要安装的工具组件(默认全选即可),等待安装完成(约10-30分钟,取决于网络速度)。
3.1.3 验证IDF环境
-
通过开始菜单打开“ESP-IDF 5.5.1 Command Prompt”,进入IDF命令行环境。
-
输入
idf.py --version,若输出“esp-idf v5.5.1”及相关版本信息,说明环境搭建成功。
3.1.4 VS Code与IDF插件配置
-
安装VS Code后,在插件市场搜索“ESP-IDF”并安装,重启VS Code。
-
按
Ctrl+Shift+P打开命令面板,输入“ESP-IDF: Configure ESP-IDF Extension”,选择“Use existing ESP-IDF directory”,指定之前安装的IDF路径(D:\ESP-IDF-5.5.1)。 -
配置完成后,通过“ESP-IDF: New Project”创建项目,验证插件是否正常工作。
3.2 RTMP服务器搭建(以Nginx-RTMP为例)
3.2.1 Windows系统搭建
-
下载已集成RTMP模块的Nginx压缩包(推荐从https://github.com/illuspas/nginx-rtmp-win32下载),解压至无中文路径(如D:\nginx-rtmp)。
-
在解压目录中找到conf/nginx.conf文件,在文件末尾添加以下配置:
rtmp { server { listen 1935; # RTMP默认监听端口 chunk_size 4096; application live { # 应用名称,需与推流地址匹配 live on; record off; # 默认关闭录像功能,可根据需要开启 } } } http { server { listen 8080; # HTTP服务端口,用于查看推流状态 location /stat { rtmp_stat all; rtmp_stat_stylesheet stat.xsl; } location /stat.xsl { root html; } } } -
双击nginx.exe启动服务器,打开浏览器访问http://localhost:8080/stat,若看到RTMP服务器状态页面,说明搭建成功。
3.2.2 云服务器搭建(可选)
若使用阿里云、腾讯云等服务器,可通过Docker快速部署:
-
安装Docker后,使用以下命令拉取镜像:
docker pull alfg/nginx-rtmp -
运行以下命令启动容器(请确保服务器已开放1935和8080端口):
docker run -d -p 1935:1935 -p 8080:80 --name nginx-rtmp alfg/nginx-rtmp
3.3 硬件连接(ESP32-S3与OV2640)
OV2640通过SPI接口与ESP32-S3连接,核心引脚对应关系如下(不同开发板引脚可能有差异,需结合硬件手册调整):
|
OV2640引脚 |
ESP32-S3引脚 |
功能说明 |
|---|---|---|
|
VCC |
3.3V |
电源(请勿接5V,避免烧毁模块) |
|
GND |
GND |
接地 |
|
SCL |
GPIO18 |
I2C时钟线,用于配置摄像头 |
|
SDA |
GPIO17 |
I2C数据线 |
|
CS |
GPIO5 |
SPI片选信号 |
|
MOSI |
GPIO11 |
SPI主机输出/从机输入 |
|
MISO |
GPIO13 |
SPI主机输入/从机输出 |
|
SCK |
GPIO12 |
SPI时钟线 |
|
RST |
GPIO4 |
摄像头复位引脚 |
|
VSYNC |
GPIO2 |
场同步信号 |

请在断电状态下连接开发板,完成接线后务必核对引脚对应关系,防止接反造成设备损坏。
四、实验流程实施
4.1 项目创建与代码集成
4.1.1 基础项目创建
- 启动ESP-IDF Command Prompt
- 进入工作目录(示例:D:\ESP32_Projects)
- 执行项目创建命令:
idf.py create-project esp32s3_rtmp_push cd esp32s3_rtmp_push - 清理默认文件:删除main目录下的app_main.c文件,为后续代码集成做准备
4.1.2 RTMP客户端库集成
-
修改项目根目录的CMakeLists.txt文件,添加组件依赖:
include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(esp32s3_rtmp_push) # 添加RTMP组件 idf_component_register( SRCS "main/rtmp_push.c" "main/camera_driver.c" INCLUDE_DIRS "main" REQUIRES esp_wifi nvs_flash driver esp_http_client rtmp ) -
在main目录下创建component_requirements.txt文件,声明依赖版本:
espressif/rtmp_client^1.0.0 -
执行配置命令自动完成库集成:
idf.py reconfigure
4.2 核心代码开发
4.2.1 WiFi连接配置(wifi_connect.c)
以下是优化后的代码实现WiFi Station模式连接:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#define WIFI_SSID "你的WiFi名称"
#define WIFI_PASS "你的WiFi密码"
#define WIFI_MAX_RETRY 5
static const char *TAG = "wifi_connect";
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < WIFI_MAX_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "尝试重新连接AP");
} else {
xEventGroupSetBits(s_wifi_event_group, BIT1);
}
ESP_LOGI(TAG, "连接AP失败");
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "获取IP: " IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, BIT0);
}
}
esp_err_t wifi_init_sta(void) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
EventBits_t bits = xEventGroupWaitBits(
s_wifi_event_group, BIT0 | BIT1, pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & BIT0) {
ESP_LOGI(TAG, "成功连接到AP SSID:%s", WIFI_SSID);
return ESP_OK;
}
else if (bits & BIT1) {
ESP_LOGI(TAG, "连接失败 SSID:%s", WIFI_SSID);
return ESP_FAIL;
}
else {
ESP_LOGE(TAG, "未知事件");
return ESP_FAIL;
}
}
4.2.2 摄像头驱动配置(camera_driver.c)
#include "driver/camera.h"
#include "esp_log.h"
static const char *TAG = "camera";
// 摄像头引脚配置
#define CAM_PIN_PWDN -1
#define CAM_PIN_RESET 4
#define CAM_PIN_XCLK 20
#define CAM_PIN_SIOD 17
#define CAM_PIN_SIOC 18
#define CAM_PIN_D7 16
#define CAM_PIN_D6 15
#define CAM_PIN_D5 14
#define CAM_PIN_D4 13
#define CAM_PIN_D3 12
#define CAM_PIN_D2 11
#define CAM_PIN_D1 10
#define CAM_PIN_D0 9
#define CAM_PIN_VSYNC 2
#define CAM_PIN_HREF 8
#define CAM_PIN_PCLK 5
// 摄像头配置参数
static camera_config_t camera_config = {
.pin_pwdn = CAM_PIN_PWDN,
.pin_reset = CAM_PIN_RESET,
.pin_xclk = CAM_PIN_XCLK,
.pin_sccb_sda = CAM_PIN_SIOD,
.pin_sccb_scl = CAM_PIN_SIOC,
.pin_d7 = CAM_PIN_D7,
.pin_d6 = CAM_PIN_D6,
.pin_d5 = CAM_PIN_D5,
.pin_d4 = CAM_PIN_D4,
.pin_d3 = CAM_PIN_D3,
.pin_d2 = CAM_PIN_D2,
.pin_d1 = CAM_PIN_D1,
.pin_d0 = CAM_PIN_D0,
.pin_vsync = CAM_PIN_VSYNC,
.pin_href = CAM_PIN_HREF,
.pin_pclk = CAM_PIN_PCLK,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_QVGA, // 320x240分辨率
.jpeg_quality = 12, // 0-63,数值越小画质越好
.fb_count = 2, // 双帧缓存
.fb_location = CAMERA_FB_IN_PSRAM, // 使用PSRAM存储
.grab_mode = CAMERA_GRAB_WHEN_EMPTY
};
esp_err_t camera_init(void)
{
// 初始化摄像头驱动
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera initialization failed: 0x%x", err);
return err;
}
// 设置摄像头参数
sensor_t *sensor = esp_camera_sensor_get();
sensor->set_framesize(sensor, FRAMESIZE_QVGA);
return ESP_OK;
}
camera_fb_t* camera_capture_frame(void)
{
return esp_camera_fb_get(); // 捕获图像帧
}
void camera_return_frame(camera_fb_t *frame)
{
esp_camera_fb_return(frame); // 释放帧缓存
}
4.2.3 RTMP推流核心逻辑(rtmp_push.c)
实现RTMP协议连接、数据封装与推流功能的核心代码如下:
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "rtmp_client.h"
#include "wifi_connect.h"
#include "camera_driver.h"
#define RTMP_URL "rtmp://your.server.ip:1935/live/esp32s3_stream" // RTMP推流地址
#define PUSH_FRAMERATE 10 // 推流帧率(FPS)
#define TASK_STACK_SIZE 1024*8
#define TASK_PRIORITY 5
static const char *TAG = "rtmp_push";
static rtmp_client_handle_t rtmp_client = NULL;
static TaskHandle_t push_task_handle = NULL;
// 初始化RTMP客户端
static esp_err_t rtmp_client_init(void) {
rtmp_client_config_t config = {
.url = RTMP_URL,
.connect_timeout_ms = 10000,
.send_timeout_ms = 5000,
};
rtmp_client = rtmp_client_create(&config);
if (!rtmp_client) {
ESP_LOGE(TAG, "Failed to create RTMP client");
return ESP_FAIL;
}
esp_err_t err = rtmp_client_connect(rtmp_client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "RTMP connect failed: 0x%x", err);
rtmp_client_destroy(rtmp_client);
rtmp_client = NULL;
return err;
}
ESP_LOGI(TAG, "RTMP connected successfully");
return ESP_OK;
}
// 推流任务主循环
static void rtmp_push_task(void *arg) {
int64_t start_time = esp_timer_get_time();
int frame_count = 0;
while (1) {
// 帧率控制
int64_t current_time = esp_timer_get_time();
int64_t elapsed_time = current_time - start_time;
int64_t expected_time = frame_count * 1000000 / PUSH_FRAMERATE;
if (elapsed_time < expected_time) {
vTaskDelay((expected_time - elapsed_time) / 1000);
continue;
}
// 获取摄像头帧数据
camera_fb_t *fb = camera_capture_frame();
if (!fb) {
ESP_LOGE(TAG, "Failed to capture frame");
vTaskDelay(100 / portTICK_PERIOD_MS);
continue;
}
// 封装RTMP视频数据包(JPEG格式)
rtmp_packet_t packet = {
.type = RTMP_PACKET_TYPE_VIDEO,
.timestamp = elapsed_time / 1000, // 时间戳(ms)
.data = fb->buf,
.size = fb->len,
.is_key_frame = 1 // JPEG帧均为关键帧
};
// 发送RTMP数据包
esp_err_t err = rtmp_client_send_packet(rtmp_client, &packet, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "RTMP push failed: 0x%x", err);
// 尝试重新连接
rtmp_client_disconnect(rtmp_client);
rtmp_client_connect(rtmp_client);
} else {
ESP_LOGI(TAG, "Pushed frame %d, size: %d bytes", frame_count, fb->len);
frame_count++;
}
// 释放帧缓冲区
camera_return_frame(fb);
}
}
// 初始化推流系统
esp_err_t rtmp_push_system_init(void) {
// 初始化WiFi连接
esp_err_t err = wifi_init_sta();
if (err != ESP_OK) {
return err;
}
// 初始化摄像头
err = camera_init();
if (err != ESP_OK) {
return err;
}
// 初始化RTMP客户端
err = rtmp_client_init();
if (err != ESP_OK) {
return err;
}
// 创建推流任务
xTaskCreatePinnedToCore(rtmp_push_task, "rtmp_push_task",
TASK_STACK_SIZE, NULL,
TASK_PRIORITY, &push_task_handle, 0);
if (!push_task_handle) {
ESP_LOGE(TAG, "Failed to create push task");
return ESP_FAIL;
}
return ESP_OK;
}
// 应用入口函数
void app_main(void) {
// 初始化NVS(用于WiFi配置存储)
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
// 初始化推流系统
err = rtmp_push_system_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Push system init failed: 0x%x", err);
}
}
4.3 项目配置(menuconfig)
-
在ESP-IDF Command Prompt中进入项目目录,执行
idf.py menuconfig打开配置界面。 -
配置摄像头相关参数: 进入“Component config” → “Camera”,勾选“Enable Camera Driver”。
-
根据硬件连接选择对应的摄像头型号(如“OV2640”),确认引脚配置与代码一致。
-
配置WiFi参数(也可在代码中直接修改): 进入“Component config” → “WiFi”,设置“WiFi SSID”和“WiFi Password”。
-
配置内存: 进入“Component config” → “ESP32S3-Specific”,根据开发板PSRAM配置选择“Support for external, SPI-connected RAM”(若有PSRAM)。
-
保存配置(按S键)并退出(按Q键)。
4.4 编译与烧录
-
将ESP32-S3开发板通过USB数据线连接到电脑,在设备管理器中确认串口已识别(如COM3)。
-
执行以下命令进行编译:
idf.py build若编译成功,会输出“Project build complete.”的提示,并显示固件路径(如build\esp32s3_rtmp_push.bin)。 -
执行烧录命令(需指定串口,替换COM3为实际串口):
idf.py -p COM3 flash烧录完成后,开发板会自动重启。 -
启动串口调试,查看日志:
idf.py -p COM3 monitor按Ctrl+]可退出串口监控。
五、实验测试与结果分析
5.1 功能测试步骤
-
启动RTMP服务器:确保Nginx-RTMP服务正常运行,访问http://服务器IP:8080/stat确认服务状态。
-
启动开发板:烧录完成后,开发板自动重启,通过串口监控查看日志,确认以下信息: WiFi连接成功,输出“got ip: xxx.xxx.xxx.xxx”。
-
摄像头初始化成功,无“Camera init failed”错误。
-
RTMP连接成功,输出“RTMP connected successfully”。
-
推流正常,输出“Pushed frame x, size: xxx bytes”。
-
客户端拉流测试: 打开VLC播放器,点击“媒体” → “打开网络串流”。
-
输入RTMP拉流地址(与推流地址一致,如rtmp://服务器IP:1935/live/esp32s3_stream),点击“播放”。
-
若测试成功,VLC将显示摄像头采集的实时图像;若失败,检查推流地址、服务器状态及网络连接。
-
稳定性测试:持续推流30分钟,观察客户端图像是否流畅,无卡顿、花屏现象,串口日志无推流错误。
5.2 常见问题与解决方法
|
问题现象 |
可能原因 |
解决方法 |
|---|---|---|
|
WiFi连接失败,提示“connect to the AP fail” |
WiFi名称/密码错误;信号弱;开发板与路由器距离过远 |
1. 检查代码或menuconfig中的WiFi参数;2. 靠近路由器测试;3. 重启路由器 |
|
摄像头初始化失败,输出“Camera init failed” |
引脚连接错误;摄像头模块损坏;供电不足 |
1. 重新检查硬件连接,对照引脚表确认;2. 更换摄像头模块测试;3. 确保供电稳定(使用外接电源) |
|
RTMP连接失败,提示“RTMP connect failed” |
推流地址错误;服务器未启动;网络不通;端口被占用 |
1. 确认RTMP地址中的IP、端口与服务器一致;2. 重启Nginx服务器;3. 测试开发板与服务器的网络连通性(ping命令);4. 检查1935端口是否被占用 |
|
VLC无法播放,无图像输出 |
推流未正常启动;拉流地址错误;防火墙拦截 |
1. 查看串口日志,确认“Pushed frame”正常输出;2. 核对拉流地址与推流地址;3. 关闭服务器与开发板的防火墙 |
|
图像卡顿、花屏 |
帧率过高;网络带宽不足;摄像头缓存配置不当 |
1. 降低推流帧率(修改PUSH_FRAMERATE);2. 减小图像分辨率(如改为FRAMESIZE_QQVGA);3. 增加JPEG压缩质量(减小jpeg_quality数值);4. 确保网络稳定 |
5.3 实验结果评估
若满足以下条件,说明实验成功:
-
开发板正常启动,WiFi、摄像头、RTMP模块初始化无错误。
-
RTMP推流稳定,串口日志持续输出推流信息,无频繁重连。
-
VLC客户端能实时拉流播放,图像清晰、流畅,无明显延迟(延迟控制在1-3秒内为正常)。
-
持续推流30分钟以上,系统无崩溃、无内存泄漏(通过IDF的heap_caps_get_free_size函数可查看内存使用情况)。
六、实验拓展与优化方向
6.1 功能拓展
-
添加音频推流:集成I2S麦克风、功放模块(如MAX9835、inmp441),采集音频数据并编码为AAC格式,通过RTMP同时推送音视频流。
-
动态调整参数:通过WiFi远程控制推流帧率、图像分辨率、JPEG质量等参数。
-
异常处理优化:增加断网重连、服务器切换、摄像头故障检测与恢复机制。
-
本地存储功能:将采集的图像数据同时保存到SD卡,实现“推流+本地备份”双重功能。
6.2 性能优化
-
内存优化:使用内存池管理图像缓存,减少内存碎片;合理配置帧缓存数量(fb_count)。
-
网络优化:开启TCP滑动窗口优化,调整RTMP包发送缓冲区大小;使用WiFi高性能模式。
-
功耗优化:在无推流需求时,将摄像头与WiFi切换到休眠模式,降低功耗(适用于电池供电场景)。
七、实验总结
本实验基于ESP32-S3和IDF5.5.1框架,完成了从环境搭建、硬件连接、代码开发到功能测试的全流程RTMP推流实现。核心难点在于摄像头驱动与RTMP协议的协同工作,以及网络连接稳定性的保障。通过本实验,可掌握ESP32-S3的外设驱动、网络通信和协议封装技术,为后续物联网视频相关项目的开发奠定基础。
实验过程中,需注重硬件连接的准确性和代码配置的一致性,遇到问题时优先通过串口日志定位故障点,结合常见问题解决方法逐步排查。对于性能与功能的优化,可根据实际应用场景灵活调整参数与拓展模块。
实验流程实施
WiFi连接配置(wifi_connect.c)
在ESP32-S3开发板中实现WiFi连接功能,需配置WiFi Station模式。以下是核心代码实现:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#define WIFI_SSID "你的WiFi名称"
#define WIFI_PASS "你的WiFi密码"
#define WIFI_MAX_RETRY 5
static const char *TAG = "wifi_connect";
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < WIFI_MAX_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "重连WiFi...");
} else {
ESP_LOGE(TAG, "WiFi连接失败");
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "获取到IP地址:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
}
}
void wifi_init_sta() {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "WiFi初始化完成");
}
摄像头驱动配置(camera_driver.c)
配置OV2640摄像头模块,初始化并捕获JPEG图像数据:
#include "esp_camera.h"
#include "esp_log.h"
#define CAM_PIN_PWDN -1 // 无电源控制引脚
#define CAM_PIN_RESET GPIO_NUM_4
#define CAM_PIN_XCLK GPIO_NUM_15
#define CAM_PIN_SIOD GPIO_NUM_17
#define CAM_PIN_SIOC GPIO_NUM_18
#define CAM_PIN_D7 GPIO_NUM_36
#define CAM_PIN_D6 GPIO_NUM_37
#define CAM_PIN_D5 GPIO_NUM_38
#define CAM_PIN_D4 GPIO_NUM_39
#define CAM_PIN_D3 GPIO_NUM_35
#define CAM_PIN_D2 GPIO_NUM_14
#define CAM_PIN_D1 GPIO_NUM_13
#define CAM_PIN_D0 GPIO_NUM_34
#define CAM_PIN_VSYNC GPIO_NUM_2
#define CAM_PIN_HREF GPIO_NUM_19
#define CAM_PIN_PCLK GPIO_NUM_21
static const char *TAG = "camera_driver";
void init_camera() {
camera_config_t config = {
.pin_pwdn = CAM_PIN_PWDN,
.pin_reset = CAM_PIN_RESET,
.pin_xclk = CAM_PIN_XCLK,
.pin_sscb_sda = CAM_PIN_SIOD,
.pin_sscb_scl = CAM_PIN_SIOC,
.pin_d7 = CAM_PIN_D7,
.pin_d6 = CAM_PIN_D6,
.pin_d5 = CAM_PIN_D5,
.pin_d4 = CAM_PIN_D4,
.pin_d3 = CAM_PIN_D3,
.pin_d2 = CAM_PIN_D2,
.pin_d1 = CAM_PIN_D1,
.pin_d0 = CAM_PIN_D0,
.pin_vsync = CAM_PIN_VSYNC,
.pin_href = CAM_PIN_HREF,
.pin_pclk = CAM_PIN_PCLK,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_SVGA,
.jpeg_quality = 12,
.fb_count = 1
};
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "摄像头初始化失败: 0x%x", err);
return;
}
ESP_LOGI(TAG, "摄像头初始化成功");
}
camera_fb_t* capture_image() {
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "图像捕获失败");
return NULL;
}
ESP_LOGI(TAG, "捕获图像成功, 大小: %d字节", fb->len);
return fb;
}
RTMP推流实现(rtmp_push.c)
实现RTMP协议封装和推流功能:
#include "rtmp_client.h"
#include "esp_log.h"
#include "freertos/task.h"
#define RTMP_URL "rtmp://your_server_ip/live/stream_key"
static const char *TAG = "rtmp_push";
void rtmp_push_task(void *pvParameters) {
rtmp_client_handle_t client = rtmp_client_init();
if (!client) {
ESP_LOGE(TAG, "RTMP客户端初始化失败");
vTaskDelete(NULL);
}
if (rtmp_client_connect(client, RTMP_URL) != ESP_OK) {
ESP_LOGE(TAG, "RTMP连接失败");
rtmp_client_destroy(client);
vTaskDelete(NULL);
}
ESP_LOGI(TAG, "RTMP连接成功");
while (1) {
camera_fb_t *fb = (camera_fb_t *)pvParameters;
if (rtmp_client_send_video(client, fb->buf, fb->len, 0) != ESP_OK) {
ESP_LOGE(TAG, "视频数据发送失败");
break;
}
vTaskDelay(pdMS_TO_TICKS(1000 / 30)); // 30fps
}
rtmp_client_destroy(client);
vTaskDelete(NULL);
}
void start_rtmp_push(camera_fb_t *fb) {
xTaskCreate(rtmp_push_task, "rtmp_push_task", 4096, fb, 5, NULL);
}
主程序整合(main.c)
整合所有功能模块,实现完整流程:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "wifi_connect.h"
#include "camera_driver.h"
#include "rtmp_push.h"
void app_main() {
ESP_LOGI("MAIN", "启动应用程序");
wifi_init_sta();
init_camera();
while (1) {
camera_fb_t *fb = capture_image();
if (fb) {
start_rtmp_push(fb);
esp_camera_fb_return(fb);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
验证与调试
服务器端验证
启动Nginx-RTMP服务器后,可通过以下方式验证推流是否成功:
- 访问
http://localhost:8080/stat查看RTMP服务器状态 - 使用VLC播放器打开网络串流,输入
rtmp://your_server_ip/live/stream_key
常见问题排查
- WiFi连接失败:检查SSID和密码是否正确,确保路由器支持2.4GHz频段
- 摄像头初始化失败:检查硬件连接,确认引脚配置与开发板匹配
- RTMP推流中断:检查网络稳定性,确保服务器端口1935已开放
性能优化建议
- 调整摄像头分辨率(如改为FRAMESIZE_VGA)降低带宽需求
- 增加帧缓冲区数量(fb_count)提升捕获效率
- 实现动态码率调整以适应网络状况变化

2532

被折叠的 条评论
为什么被折叠?



