十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在 ESP32 上构建 RTSP 视频流服务器:深入解析 Micro-RTSP 库的原理与 Tasmota 集成实践

在 ESP32 上构建 RTSP 视频流服务器:深入解析 Micro-RTSP 库的原理与 Tasmota 集成实践 在 ESP32 上构建 RTSP 视频流服务器深入解析 Micro-RTSP 库的原理与 Tasmota 集成实践【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota导读Micro-RTSP 是一个专为资源受限 MCU 设计的轻量级 RTSP 流媒体库能够让 ESP32 这样的低成本芯片直接对外提供标准的 RTSP 视频流服务——这也是其 README 中用约 10 美元的硬件做一台开源 RTSP 摄像头这一目标的由来。本文以该库的 README 为主线结合仓库中lib/libesp32/rtsp/的完整源码讲解 RTSP 握手流程、RTP/JPEG 分片打包等底层原理并展示该库在 Tasmota 固件中xdrv_81_esp32_webcam.ino的真实接入方式。读完本文你将掌握如何在自己的 ESP32 项目里集成 Micro-RTSP、如何扩展新摄像头驱动以及 RTSP 流服务从 TCP 接受到 RTP 帧发送的完整调用链。Micro-RTSP 是什么为 MCU 而生的微型 RTSP 服务器RTSPReal Time Streaming Protocol是控制音视频流的会话级协议与负责实际传输的 RTPReal-time Transport Protocol配合使用。主流的 RTSP 服务器如 Live555、GStreamer 生态体积庞大、依赖复杂难以跑在只有几百 KB RAM 的 ESP32 上。Micro-RTSP 正是为此设计的替代方案极简体积核心仅有CRtspSession会话管理与CStreamer流媒体发送两个抽象加上平台适配层platglue整个库只有十几个源文件标准兼容实现了OPTIONS、DESCRIBE、SETUP、PLAY、TEARDOWN五个 RTSP 核心命令见 CRtspSession.h 中的RTSP_CMD_TYPES枚举可被 VLC、ffplay、Onvif 客户端等标准播放器直接拉流跨平台通过 platglue.h 在编译期切换platglue-esp32.hESP32/Arduino与platglue-posix.hPOSIX 系统面向资源受限设备单线程事件循环驱动不需要操作系统线程注释中反复强调we assume single threaded大缓冲区刻意放在对象成员而非栈上以规避 MCU 上极小的栈空间限制。在 Tasmota 仓库中该库被用于 xdrv_81_esp32_webcam.ino 的 ESP32 摄像头功能ENABLE_RTSPSERVER编译开关让 Tasmota 设备同时具备 MJPEG Web 预览和标准 RTSP 拉流两种能力。快速上手在 PlatformIO 工程中接入 Micro-RTSPREADME 给出的接入方式非常直接——如果你的工程基于 PlatformIO一条命令即可安装pio lib install Micro-RTSP若需要使用 OV2640 摄像头支持OV2640Streamer工程必须面向espressif32平台因为 OV2640 驱动依赖乐鑫官方esp32-camera库的esp_camera_fb_get()/esp_camera_fb_return()接口见 OV2640.cpp。如果希望像 Tasmota 一样直接以内置源码方式使用只需将 lib/libesp32/rtsp 整个目录放入工程的lib/下即可。安装后一个最小的 RTSP 摄像头服务器由四个步骤组成README 原文要点在 RTSP 端口上通过accept()监听 TCP 连接连接建立后创建CRtspSession会话对象和OV2640Streamer或SimStreamer流对象连接存续期间循环调用session-handleRequests(0)处理客户端请求以约 100ms 为周期调用session-broadcastCurrentFrame()向客户端推送新帧。README 中给出的loop()参考实现完整复现如下void loop() { uint32_t msecPerFrame 100; static uint32_t lastimage millis(); // If we have an active client connection, just service that until gone // (FIXME - support multiple simultaneous clients) if(session) { session-handleRequests(0); // we dont use a timeout here, // instead we send only if we have new enough frames uint32_t now millis(); if(now lastimage msecPerFrame || now lastimage) { // handle clock rollover session-broadcastCurrentFrame(now); lastimage now; // check if we are overrunning our max frame rate now millis(); if(now lastimage msecPerFrame) printf(warning exceeding max frame rate of %d ms\n, now - lastimage); } if(session-m_stopped) { delete session; delete streamer; session NULL; streamer NULL; } } else { client rtspServer.accept(); if(client) { //streamer new SimStreamer(client, true); // our streamer for UDP/TCP based RTP transport streamer new OV2640Streamer(client, cam); // our streamer for UDP/TCP based RTP transport session new CRtspSession(client, streamer); // our threads RTSP session and state } } }这段代码有三个值得注意的工程细节零超时轮询handleRequests(0)传入 0 毫秒读超时表示非阻塞尝试读取配合主循环外部对帧速率的节流msecPerFrame 100避免在阻塞读上浪费时间时钟回绕保护now lastimage msecPerFrame || now lastimage中后半段处理了millis()约 49.7 天的回绕场景单客户端模型代码注释FIXME - support multiple simultaneous clients明确说明当前实现假定同一时刻只有一个活跃客户端——这是微型定位的刻意取舍播放器断连后m_stopped置位触发对象销毁并回到accept()监听。RTSP 握手内部五个命令的状态机CRtspSession是协议层的核心。它的工作方式是handleRequests()从 socket 读取请求CRtspSession.cpp 中按首字母粗过滤O/D/S/P/T再由ParseRtspRequest()解析命令类型、URL、CSeq序号与Content-Length最后分发到对应的Handle_RtspXxx()处理器。一次完整的 RTSP 拉流客户端会依次发起以下命令命令处理函数服务端行为OPTIONSHandle_RtspOPTION返回Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE声明能力DESCRIBEHandle_RtspDESCRIBE校验流 URL 是否为mjpeg/1、mjpeg/2返回携带 SDP 描述mvideo 0 RTP/AVP 2626 即 JPEG 动态载荷类型的 200 OK否则返回 404SETUPHandle_RtspSETUP解析client_port调用m_Streamer-InitTransport()初始化 RTP/RTCP 端口协商 UDP 或 TCP 传输PLAYHandle_RtspPLAY返回RTP-Info与SessionID同时将m_streaming置trueTEARDOWN在handleRequests内将m_stopped置true主循环据此销毁会话DESCRIBESDP 描述与流 URL 校验Handle_RtspDESCRIBECRtspSession.cpp先校验 URL 路径mjpeg/1对应流 0mjpeg/2对应流 1其余一律 404。成功后生成如下 SDPv0 o- 1426346102 1 IN IP4 192.168.1.100 s t0 0 mvideo 0 RTP/AVP 26 cIN IP4 0.0.0.0其中t0 0表示无边界永久会话mvideo 0 RTP/AVP 26中的载荷类型 26 是 IANA 为 JPEG-over-RTPRFC 2435分配的动态类型编号。这段 SDP 不含ax-dimensions尺寸行源码中已注释尺寸信息实际由 RTP 包内的 JPEG 载荷头携带。SETUPUDP 与 TCP 两种传输模式的协商Handle_RtspSETUPCRtspSession.cpp首先在请求中检索client_port形如client_port6970-6971解析出 RTP 端口RTCP 端口为 RTP1。随后在请求中查找RTP/AVP/TCP关键字来决定传输模式TCP 模式响应Transport: RTP/AVP/TCP;unicast;interleaved0-1RTP 数据复用 RTSP 的 TCP 连接UDP 模式响应Transport: RTP/AVP;unicast;...;client_port6970-6971;server_port8554-8555服务端在InitTransport()CStreamer.cpp中从 6970 起按偶数递增扫描尝试绑定 RTP/RTCP 端口对。PLAY 与 TEARDOWN会话状态翻转Handle_RtspPLAY返回Range: npt0.000-、Session: id和RTP-Info行并由handleRequests()将m_streaming置位此后broadcastCurrentFrame()CRtspSession.cpp才会真正向流对象请求帧数据。TEARDOWN则将m_stopped置位驱动主循环完成资源回收。RTP/JPEG 打包RFC 2435 的完整落地协议层之上CStreamer负责把一帧 JPEG 拆成若干个 RTP 包发出完整实现了 RFC 2435JPEG-over-RTP的关键细节。核心函数SendRtpPacketCStreamer.cpp的封包结构为------------------------------------------------------ | 4 字节 RTP-over-RTSP 帧头TCP 模式$ 通道 长度| ------------------------------------------------------ | 12 字节 RTP 头版本、载荷类型 26、序号、时间戳、SSRC| ------------------------------------------------------ | 8 字节 JPEG 载荷头分片偏移、类型、质量因子、宽高 | ------------------------------------------------------ | 可选 量化表头 2×64 字节量化表仅首分片 | ------------------------------------------------------ | JPEG 扫描数据分片单片最大 1100 字节 | ------------------------------------------------------几个实现要点分片机制MAX_FRAGMENT_SIZE 1100一帧较大的 JPEG 会被拆成多个 RTP 包3 字节的分片偏移fragmentOffset让接收端可以重组最后一个分片置 RTP 头中的 Mmarker位0x1a | (isLastFragment ? 0x80 : 0x00)量化表随包发送decodeJPEGfile()从 JPEG 流中提取两张 DQT 量化表仅在帧首分片中按 RFC 2435 的MBZ 精度 长度 表数据格式携带质量因子字节q在有量化表时为128否则为0x5e时间戳模型streamFrame()CStreamer.cpp以 90 kHz 时钟units 90000RFC 2435 标准按curMsec增量推进时间戳并谨慎处理了millis()回绕回绕时按 100ms 估算JPEG 流裁剪decodeJPEGfile()依次定位 SOI0xd8、DQT0xdb、SOS0xda、EOI0xd9标记把首尾无用数据裁掉只发送真正的扫描数据*len endmarkerptr - *start。扩展新摄像头实现 streamImage() 即可README 明确指出支持新摄像头非常简单参照OV2640Streamer实现streamImage()方法——从你的摄像头读出一帧交给基类发送。以 OV2640Streamer.cpp 为例其完整实现只有 7 行void OV2640Streamer::streamImage(uint32_t curMsec) { m_cam.run();// queue up a read for next time BufPtr bytes m_cam.getfb(); streamFrame(bytes, m_cam.getSize(), curMsec); }其中m_cam.run()负责把上一帧 buffer 归还驱动esp_camera_fb_return并抓取新帧esp_camera_fb_get。接入新传感器时只需定义自己的MyCamStreamer : public CStreamer在streamImage()中取得原始 JPEG 字节并调用受保护的streamFrame(data, len, curMsec)——后续的分片、打包、时间戳全部由基类完成。若暂时没有真实摄像头可用库自带的SimStreamerREADME 示例代码中即以注释形式给出发送内置测试 JPEG 数据方便先打通整条 RTSP 链路。摄像头配置与引脚映射OV2640.cpp内置了三套 ESP32-CAM 开发板的引脚配置配置对象适用板卡关键引脚差异esp32cam_config经典 ESP32-CAM devboard 及多数克隆板pin_pwdn-1,pin_reset15, XCLK27esp32cam_aithinker_configAi-Thinker 变体pin_pwdn32,pin_reset-1, XCLK0esp32cam_ttgo_t_configTTGO T-Journalpin_pwdn26,pin_reset-1, XCLK32三套配置共同的帧参数为PIXFORMAT_JPEG像素格式、FRAMESIZE_SVGA800×600、jpeg_quality120–63数值越低画质越高、fb_count2双缓冲使 I2S 连续模式可用。源码注释还提示了更小分辨率的可能性FRAMESIZE_XGA需要约 96 KB 帧缓冲单帧缓冲即可工作。注意这里的引脚定义是库作者为裸机 PlatformIO 工程准备的默认值Tasmota 集成时会在自己的摄像头初始化流程中另行配置引脚。Tasmota 中的实战集成从 README 到生产固件README 给出的示例是独立工程的形态而在本仓库中Micro-RTSP 已被真正集成进 Tasmota 固件二者代码结构几乎逐行对应是学习如何把该库嵌入真实产品的最佳范本。在 xdrv_81_esp32_webcam.ino 的WcLoop()中ENABLE_RTSPSERVER编译开关保护下的逻辑与 README 示例同构if (Settings-webcam_config.rtsp !TasmotaGlobal.global_state.wifi_down Wc.up) { if (!Wc.rtsp_start) { Wc.rtspp new WiFiServer(8554); // RTSP 标准端口 Wc.rtspp-begin(); Wc.rtsp_start 1; ... } if (Wc.rtsp_session) { Wc.rtsp_session-handleRequests(0); uint32_t now millis(); if ((now-Wc.rtsp_lastframe_time) RTSP_FRAME_TIME) { Wc.rtsp_session-broadcastCurrentFrame(now); Wc.rtsp_lastframe_time now; } if (Wc.rtsp_session-m_stopped) { delete Wc.rtsp_session; delete Wc.rtsp_streamer; Wc.rtsp_session NULL; Wc.rtsp_streamer NULL; ... } } else { Wc.rtsp_client Wc.rtspp-accept(); if (Wc.rtsp_client) { Wc.rtsp_streamer new OV2640Streamer(Wc.rtsp_client, Wc.cam); Wc.rtsp_session new CRtspSession(Wc.rtsp_client, Wc.rtsp_streamer); ... } } }相比 README 示例Tasmota 的工程化增强包括端口标准化监听 8554RTSP 事实标准端口而非 README 示例中未显式给出的端口状态保护增加了wifi_down、Wc.up摄像头初始化完成等前置条件避免断网或摄像头未就绪时空转节流常量RTSP_FRAME_TIME替代硬编码的 100ms帧率可配置与 MJPEG 共存Tasmota 同时以:81/stream提供 MJPEG Web 预览WcShowStream()中通过img srchttp://%_I:81/stream嵌入网页RTSP 只是其中一种流输出。从源码结构看Wc.rtsp_session、Wc.rtsp_streamer、Wc.rtsp_client均为全局结构体Wc的成员与WiFiServer一起构成了 Tasmota 侧对 Micro-RTSP 的完整封装。POSIX 平台的移植用法README 说明该库同样适用于大多数 POSIX 平台并提供了独立示例 test/RTSPTestServer.cpp位于仓库lib/libesp32/rtsp/test/下构建步骤见同目录 test/README.md。CRtspSession与SimStreamer两个核心类的用法与 ESP32 完全一致差异仅在底层 socket 实现——通过 platglue-posix.h 将socketsend、socketread、udpsocketsend等抽象映射到 BSD socket API。这意味着你可以在开发机上先用测试服务器调试 RTSP 客户端行为再原样交叉编译到 ESP32。结构与设计取舍总结对照 README 的Structure and design notes以及源码Micro-RTSP 的设计哲学可以归纳为单线程事件循环会话处理、RTP 发送全部在主循环内完成无锁、无阻塞线程天然适配loop()式 MCU 编程模型内存即资源RTSP_BUFFER_SIZE 10000、RtpBuf[2048]等大缓冲区全部作为对象成员而非栈变量源码多处注释we assume single threaded, this large buf we keep off of the tiny stack这是 MCU 移植的关键技巧功能裁剪有度暂不支持多客户端并发、不支持音频/非 JPEG 载荷但在10 美元 RTSP 摄像头这一目标场景下做到标准兼容、可直接被通用播放器消费。如果你正打算为 ESP32-CAM 或同类低成本板卡提供 RTSP 拉流能力可以直接复用本仓库的 lib/libesp32/rtsp 库MIT 许可版权与许可全文见 LICENSE参照 OV2640.cpp 的引脚配置与 xdrv_81_esp32_webcam.ino 的集成模式即可快速落地一套端到端可用的 RTSP 摄像头固件。【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表