ESP32-S3视频流拉取及LCD显示

文章介绍了如何使用ESP32通过WIFI获取其他设备的摄像头视频流,并在本地4.3寸LCD上显示,通过Mjpg-streamer、HTTP-GET、JPEG解码和双线程优化技术提升帧率。作者还讨论了分辨率和内存优化策略,以适应硬件限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 背景介绍

目前ESP32模组中自带摄像头、lcd、WIFI模组、PSRAM等硬件资源,并依靠其大量的例程资源,使其在嵌入式开发领域的应用范围不断在扩展,不过目前,ESP32根据本身的硬件资源,只能支持一些分辨率较小的LCD显示,例如128*160分辨率。大量的应用方案是利用ESP32的摄像头模组通过WIFI传输,在电脑上进行实时显示。本文中的方案正好反过来,本ESP32通过WIFI-http-get方式,拉取其他设备上摄像头的视频流并在本地4.3寸LCD屏上显示,同时为了实现较高分辨率较高帧率的效果做了一系列的调试。

2 系统框架

电脑USB接摄像头,提供Mjpg-streamer服务。ESP32通过WIFI连接电脑的热点,以http-get的方式获取Mjpg-streamer-URL地址上的实时视频流,并通过解码实时显示在本地4.3寸屏幕上。

3 功能描述

3.1 Mjpg-streamer与HTTP-GET视频流

Mjpg-streamer可以在配置文件中修改输入的视频流尺寸、视频流类型、http-get的返回报文等参数。

int width = 320, height = 240, fps = -1, format = V4L2_PIX_FMT_MJPEG, i;

http-get的返回报文:状态码、一帧图片类型、大小、时间戳、数据等信息。

output_http/httpd.c

DBG("preparing header\n");
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
        "Access-Control-Allow-Origin: *\r\n" \
        STD_HEADER \
        "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \
        "\r\n" \
        "--" BOUNDARY "\r\n");
.
.
.
sprintf(buffer, "Content-Type: image/jpeg\r\n" \
        "Content-Length: %08d\r\n" \
        "X-Timestamp: %06d.%06d\r\n" \
        "\r\n", frame_size, (int)timestamp.tv_sec, (int)timestamp.tv_usec);
DBG("sending intemdiate header\n");
if(write(context_fd->fd, buffer, strlen(buffer)) < 0) break;

DBG("sending frame\n");
if(write(context_fd->fd, frame, frame_size) < 0) break;

DBG("sending boundary\n");
sprintf(buffer, "\r\n--" BOUNDARY "\r\n");
if(write(context_fd->fd, buffer, strlen(buffer)) < 0) break;
 

ESP32在发送http-get请求后,将对返回的报文中的内容进行解析,并根据一帧图片大小接收图片数据,传至图片解码函数。

HTTP-GET阻塞-解码刷屏示意图

3.2 jpeg解码并显示

jpeg解码中,因为受到ram大小的限制,将一帧图片分段进行解码并显示,循环解码:一个像素RGB3个字节转2字节565形式。

实际在对(640*480)分辨率的调试中,帧率受硬件限制最高只有8帧,无法达到流畅的效果。后来在解码中加入像素插值,用低画质(320*240)数据显示大图片(640*480),才能达到较高的帧率以及相对流畅的效果。

像素插值流程图

O

X

O

X

O

X

O

X

O

X

X

X

X

X

X

X

X

X

X

X

O

X

O

X

O

X

O

X

O

X

X

X

X

X

X

X

X

X

X

X

像素插值示意图行列放大一倍

jpegd2.c

while (cinfo.output_scanline < cinfo.output_height) {
        (void) jpeg_read_scanlines(&cinfo, buffer, 1);
        uint8_t *inbuffer = buffer[0];
        uint32_t index;
        static uint32_t index_last = 0;
        uint32_t y = cinfo.output_scanline;
        uint16_t *out = (uint16_t *)outbuffer;// + (y * cinfo.output_width);
        for (index = 0; index < cinfo.output_width*WIDTH_PLUS; index++) {
        uint16_t c = ((*inbuffer) >> 3) << 11 | ((*(inbuffer + 1)) >> 2) << 5 | (*(inbuffer + 2)) >> 3;
        out[index_last+index] = c;
#if HIGHT_PLUS == 2     //根据宏定义屏幕宽度的放大倍数进行像素插值
        out[index_last+index+CONFIG_LCD_BUF_WIDTH*2] = c;
#endif
#if WIDTH_PLUS == 2
        index++;
        out[index_last+index] = c;
#endif
#if HIGHT_PLUS == 2        
        out[index_last+index+CONFIG_LCD_BUF_WIDTH*2] = c;
#endif
        inbuffer += 3;
        }
        index_last += index*HIGHT_PLUS;
        if(!(y % CONFIG_LCD_BUF_HIGHT) || index_last >= (CONFIG_LCD_BUF_HIGHT*cinfo.output_width*WIDTH_PLUS*HIGHT_PLUS)){
            lcd_cb(0, (y-CONFIG_LCD_BUF_HIGHT)*HIGHT_PLUS, CONFIG_LCD_BUF_WIDTH*WIDTH_PLUS, CONFIG_LCD_BUF_HIGHT*HIGHT_PLUS, outbuffer);
            index_last = 0;
        }
    }

刷屏调用ESP32库函数esp_lcd_panel_draw_bitmap()分段显示。

3.3 双线程加队列优化

以320*240(8KB)一帧的JPEG为例,http-get需要40~50ms,jpeg解码显示需要40~50ms,通过一个线程负责http-get,另一个线程负责jpeg解码显示,中间通过队列传递一帧的数据,使得帧率比单线程顺序执行提高20%,同时需要占用较大空间的ram。

双线程HTTP-get和解码流程图

3.4 LVGL的显示效果

这里引入LVGL是为了测试LVGL驱动的刷屏效果,同时利用屏幕剩余空间增加一些控制按钮。LVGL目前没有接入视频流的库函数,于是将jpeg解码出的RAW数据用LVGL刷新图片的方式一帧帧刷新出来,中间需要利用ESP32较大的PSRAM来当作缓存,实际的帧率为原先的1/2。

图中视频流为640*480 jpeg(通过LVGL驱动显示)

3.5 图像大小-处理时间优化

4.3寸lcd的分辨率为800*480,最大显示实时视频流的分辨率为640*480。然而一帧640*480jpeg数据大小在30k~100k(受摄像头-图像质量以及整体亮度的影响差距较大),分段刷新中一段640*48的图像解码出来大概61k,需要根据内部ram空间对一段的大小进行适当调整。一帧jpeg图片在http-get和解码显示中的耗时与图片的大小成正比,640*480的像素是320*240的4倍,这直接导致在刷新过程中480P帧率只有240P的1/4。

ESP32-S3 240MHZ 512 KB SRAM 双核

4.3寸lcd 8080 16位并口 --JPEG

Board

640*480

640*480(lvgl)

320*240

320*240(插值放大至640*480)

图片大小

30KB

30KB

8KB

8KB

帧率

8

4

22

22

表中数据为不同分辨率与刷新方式下的实时显示帧率

图中视频流为320*240 jpeg

图中视频流为320*240 jpeg(通过插值放大至640*480)

4 总结

本案例充分开发了ESP32-S3的SRAM、PSRAM、双核CPU资源,以及配套4.3寸800*480LCD的刷屏极限,通过双线程加队列方式能够提高20%的帧率,不过由于硬件限制大部分时间消耗在解码过程中,而分辨率越高消耗的时间就越大,目前实际采用320*240的视频流通过插值放大至640*480像素的方案,在4.3寸LCD能够得到平均22帧相对流畅的效果,可以满足一些对分辨率要求不高的视频相关应用开发。

### ESP32-S3 实现视频播放的方法 对于基于 ESP32-S3 的 Walter 开发板而言,实现视频播放涉及多个组件和技术栈的协同工作。由于该开发板具备较强的处理能力和丰富的外设接口,理论上支持通过特定方式完成视频解码与显示。 #### 方法概述 为了在 ESP32-S3 上实现视频播放,通常采用如下方案: - **硬件准备**:确保拥有合适的显示屏模块连接至 ESP32-S3LCD 接口[^3]。 - **软件框架选择**:推荐使用 LVGL 图形库来简化 GUI 设计过程,并利用其内置或扩展的功能来进行图像渲染操作。LVGL 支持多种输入设备以及图形加速器,在资源有限的情况下仍能提供流畅体验。 - **视频源获**:可以通过 SPIFFS 文件系统存储预录制好的 MP4 或其他格式的小型视频文件;也可以考虑从网络流媒体服务器实时数据流作为动态内容来源。 - **编解码器集成**:鉴于 ESP32-S3 自身并不直接支持硬解 H.264 等常见编码标准,因此需借助第三方开源项目如 FFmpeg Lite 来加载并解析这些多媒体容器内的帧序列,再逐帧传递给显示缓冲区更新画面。 #### 示例代码展示 下面给出一段简单的 Python 风格伪代码用于说明上述流程中的核心逻辑部分(实际应用时应转换成 C/C++ 并适配具体的 SDK 版本): ```cpp #include "lvgl/lvgl.h" extern void ffmpeg_lite_decode_frame(const char *video_path, uint8_t* frame_buffer); void setup() { // 初始化串行通信、摄像头、WiFi 及其他必要服务... lv_init(); disp_drv_register(&my_disp); // 注册显示器驱动程序 const char* video_file = "/spiffs/sample.mp4"; } void loop() { static bool is_playing = false; if (!is_playing && /* 用户触发条件 */) { is_playing = true; while (is_playing) { uint8_t buffer[BUFFER_SIZE]; // 解码一帧视频到内存中 ffmpeg_lite_decode_frame(video_file, buffer); // 将解码后的 RGB 数据发送到屏幕刷新区域 lv_img_set_src(my_image_object, buffer); delay(1000 / FPS_RATE); // 控制每秒帧率 yield(); // 让出 CPU 时间片以便响应中断事件 } // 清理收尾工作... } } ``` 此段代码仅作概念验证用途,请读者自行完善细节以适应具体应用场景需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值