ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ESP32摄像头实现ONVIF协议接入NVR的完整开发指南

ESP32摄像头实现ONVIF协议接入NVR的完整开发指南 一台带摄像头的ESP32要让市场上的成品NVR直接搜到、顺利添加、稳定出画面关键不在于摄像头驱动而在于你给它配齐了“网络身份”——也就是ONVIF协议栈。我前面做完这个onvif-c组件的时候最直观的感受是NVR端逻辑其实非常简单它只会问你几个固定问题答对了就收编答错一个就反复报“设备离线”或者“通道添加失败”。这篇文章就是把整条链路从ESP-IDF工程搭建、C语言组件设计到WS-Discovery发现、设备管理、Media服务、RTSP拉流再到真机联调做成一份可以直接照着做的参考手册。这套方案适用于想用ESP32系列做主控、做低成本IP相机或改造现有摄像头产品的开发者。前置技能只需要基础的C语言和ESP-IDF使用经验。如果你想弄明白NVR背后的发现机制和协议交互这篇文章同样值得读一遍。1. 项目全景ONVIF-C组件要做成什么样1.1 需求拆解什么才算“能被NVR添加的相机”先下一个明确的技术定义一台网络摄像机要被NVR添加必须满足三件事。第一件事是“被发现”。NVR启动后会在局域网内发送WS-Discovery的UDP多播探测Probe所有支持ONVIF的设备收到后必须回一个ProbeMatch消息在里面带上自己的设备服务地址XAddrs。这一步决定了NVR的设备列表里会不会出现你的相机。第二件事是“被识别”。NVR拿到地址后会通过HTTP调用ONVIF的Device Management服务依次询问设备信息、能力集、时间。这些接口返回的XML必须严格符合ONVIF定的Soap消息格式稍有偏差异常NVR就会判定设备不合法或干脆跳过。第三件事是“能拉流”。NVR会继续调用Media服务查询媒体配置描述GetProfiles拿到RTSP拉流地址GetStreamUri再按RTSP协议发起实时流会话。如果RTSP交互不规范或视频流格式不兼容NVR即使成功添加通道也只会显示黑屏或反复重连。所以ONVIF-C组件本质上不是一个庞大复杂的协议实现而是一组小而准确的“应答逻辑”。只要把发现、设备管理、媒体服务、RTSP四块串起来并保证每个环节的消息格式正确这台ESP32相机就能跟任何支持ONVIF的NVR对话。1.2 技术选型为什么从零写一个C语言ONVIF组件市面上已有的ONVIF实现大部分是C的动辄依赖boost、gSOAP或者OpenSSL在ESP32这种资源受限的MCU上很难跑得舒服。我自己当初在A开发者那里接手这个需求时也考虑过直接移植某个开源栈结果光是交叉编译依赖项就耗了一整天生成的中间层代码体积还特别大flash直接就超了。用纯C手写ONVIF协议栈看起来“多干活”其实是更适配MCU的做法依赖可控。只需要ESP-IDF自带的lwIP网络协议栈、HTTP Server组件和一套轻量级XML解析器不引入额外重量级依赖。内存占用小。ONVIF消息本质上是SOAP/XML文本单条交互消息大概1到4KB完全可以用静态缓冲区处理不需要动态内存分配太多。行为透明。每个接口干什么、返回什么XML自己写了之后心里一清二楚。排查NVR兼容性问题时可以直接定位到消息内容而不是去读第三方库生成的晦涩代码。我还特意把组件设计成独立目录不跟上层业务代码耦合。这样一来后续想移植到别的芯片平台或者把视频流从MJPEG换成H.264只需要替换采集和RTSP打包模块ONVIF协议交互部分完全不用动。1.3 适用场景与前置技能如果你做的是如下任意一类产品这套路线可以直接参考带摄像头的ESP32开发板做原型验证给自己手头的智能家居摄像头固件增加ONVIF兼容基于ESP32-S3做低成本IPC希望能在第三方NVR和录像软件里正常显示用ESP32做视频传感器节点想接入统一的监控平台。前置技能上你需要能完成ESP-IDF的标准环境搭建和编译烧录理解uDP/TCP基础网络模型了解线程和队列。XML方面则可以边写边学我会在模块拆解时给出具体的解析思路。2. 从零建工程ESP-IDF项目结构与组件化设计2.1 工程骨架与组件目录布局我习惯把整个固件工程分成main和components两层。main只负责系统初始化、网络连接、启动摄像头任务所有跟ONVIF和RTSP相关的代码都收进components目录里的独立组件中。一个典型的目录结构如下my_camera/ ├── main/ │ ├── CMakeLists.txt │ └── app_main.c ├── components/ │ └── onvif-c/ │ ├── CMakeLists.txt │ ├── include/ │ │ ├── onvif.h │ │ ├── onvif_discovery.h │ │ ├── onvif_device.h │ │ ├── onvif_media.h │ │ ├── rtsp_server.h │ │ └── jpeg_stream.h │ ├── src/ │ │ ├── onvif_discovery.c │ │ ├── onvif_device.c │ │ ├── onvif_media.c │ │ ├── onvif_soap.c │ │ ├── onvif_digest_auth.c │ │ ├── rtsp_server.c │ │ ├── jpeg_rtp.c │ │ └── camera_capture.c │ └── example/ │ └── onvif_component_test.c └── CMakeLists.txt这个结构的好处是清晰划分了职责。onvif_discovery管UDP多播发现onvif_device管设备管理与认证onvif_media管Profile和StreamUri的回应rtsp_server管实时流会话jpeg_rtp负责把JPEG帧打包成RTP包。任何人接手代码只看文件名字就能猜出模块用途。CMakeLists.txt里最关键的写法是设置组件依赖。组件需要的只是ESP-IDF自带能力不需要额外下载库idf_component_register( SRCS src/onvif_discovery.c src/onvif_device.c src/onvif_media.c src/onvif_soap.c src/onvif_digest_auth.c src/rtsp_server.c src/jpeg_rtp.c src/camera_capture.c INCLUDE_DIRS include REQUIRES esp_wifi esp_event lwip esp_http_server esp_system )注意这里需要REQUIRES里带上esp_http_serverONVIF的HTTP服务就是基于它的lwip提供UDP多播socket能力摄像头驱动如果单独拉成组件也需要在依赖列表里体现我实际工程里还加了一个camera_driver组件。2.2 C语言模块划分连接“协议”和“硬件”模块划分的关键是让“协议层”不直接触碰“硬件层”。我引入了两个内部接口抽象一个是device_info_t集中存放设备序列号、制造商、固件版本、网络地址另一个是stream_source_t对外输出一帧JPEG数据、帧大小和时间戳。协议层只依赖这两个结构体不关心摄像头是OV2640、OV5640还是别的型号。摄像头采集模块初始化传感器把帧数据填到stream_source_t里上层RTSP打包时直接从里面取。万一以后要换编码方式或者加一个AI图像预处理环节都只改采集侧不动协议交互。我在具体编码时还做了一个统一的消息回调机制。每个ONVIF请求进来后onvif_soap模块先解析出方法名再dispatch到对应的处理函数处理函数构造响应XML交还给HTTP服务层。整体控制流就像一张简单的映射表GetDeviceInformation → 填充制造商、型号、固件版本GetProfiles → 返回所有Profile的Token和编码格式GetStreamUri → 返回rtsp://ip:port/live0的地址GetCapabilities → 返回Media和设备的服务地址SystemReboot → 触发系统重启这张表要跟ONVIF的Profile S规范逐条核对宁可少实现也不能返回错误格式的字段。2.3 构建配置与内存规划ESP32跑ONVIF服务除了代码逻辑内存预算也要单独算。我把网络收发缓冲区和协议XML缓冲区按如下方式分配HTTP服务默认配置栈大小6144字节max_uri_handlers至少20个SOAP请求缓冲区3KB足以容纳一次GetProfiles请求SOAP响应缓冲区4KBProfile信息多时响应量大JPEG帧缓冲双缓冲各32KB左右按摄像头分辨率调整RTSP会话列表最多支持2路并发会话这里最容易被忽略的是HTTP服务器在处理大请求时的栈空间。ONVIF的SOAP请求通常会带多行XML头esp_http_server默认的接收缓冲如果太小会出现请求被截断的问题。我实测下来栈大小给到8KB以上处理大消息时才稳定。3. ONVIF协议栈实现让NVR“看得见、认得出”3.1 WS-Discovery设备发现Probe与ProbeMatchONVIF设备发现基于WS-Discovery协议本质是UDP多播。NVR往239.255.255.250:3702发送一条Probe报文要求局域网内所有符合某类型或某作用域的设备回应。相机的职责是监听这个多播端口解析Probe回一条ProbeMatch。Probe消息的SOAP订阅类型Types一般是“dn:NetworkVideoTransmitter”作用域Scopes里通常带有设备位置信息。我用的实现思路是不解析Types的复杂语义只看Probe里的MessageID然后原样复用它的MessageID和相关字段来构造ProbeMatch。这段核心代码的逻辑可以简化成static void on_probe_received(int sock, struct sockaddr_in *src) { char probe_msg[2048]; char message_id[128]; int len recvfrom(sock, probe_msg, sizeof(probe_msg), 0, src, src_len); if (parse_message_id(probe_msg, message_id, sizeof(message_id)) ! ESP_OK) { return; } if (strstr(probe_msg, NetworkVideoTransmitter) NULL) { return; } build_probe_match_response(message_id, src-sin_addr, src-sin_port); }ProbeMatch里必须正确填写XAddrs字段这个地址是NVR后续访问设备的入口必须能被NVR直接访问。我踩过的坑是一开始填写了开发板从路由器拿到的IP结果有线无线混用环境下NVR在另一个网段只能换用广播可达的IP。最终解决办法是读取当前所有网卡的有效IP优先选择跟多播包进入的网卡同网段的IP填入XAddrs。3.2 SOAP/XML消息的构造与解析整个ONVIF交互过程基本都是构造XML字符串和解析XML字符串。手写一个完整的XML DOM解析器在MCU上不现实但好消息是ONVIF的消息结构非常固定我们只需按模式匹配提取关键字段。我实现了一个轻量的解析函数库提供两类能力按标签路径取文本内容比如取Envelope.Body.GetDeviceInformationResponse.Manufacturer以及按标签名列表直接拼接响应XML片段。这个设计比引入expat库更省内存也比较容易排查错误。解析时的几个注意点命名空间前缀可能变。同一份消息有的NVR发来用ns1:有的用tds:。解析时不要硬匹配前缀只关注标签名的本地名称。大小写必须敏感。GetProfilesResponse和Getprofilesresponse不是同一个东西。XML里经常带引号转义和实体会话标记提取文本时要先做一次unescape。响应XML的构造更需要耐心。ONVIF要求的根节点是SOAP-ENV:Envelope必须包含命名空间声明。设备服务的GetProfilesResponse结构尤其严谨每个Profile里要列出VideoEncoderConfiguration的编码类型、分辨率、帧率、比特率这些参数在后续RTSP协商时也要保持一致。我最终将响应构造函数都收敛成类似这样的形式char *build_get_profiles_response(char *buf, int buf_size) { snprintf(buf, buf_size, ?xml version\1.0\ encoding\UTF-8\? SOAP-ENV:Envelope xmlns:SOAP-ENV\http://www.w3.org/2003/05/soap-envelope\ xmlns:trt\http://www.onvif.org/ver10/media/wsdl\ xmlns:tt\http://www.onvif.org/ver10/schema\ SOAP-ENV:Body trt:GetProfilesResponse trt:Profiles token\MainStream\ tt:Namemain/tt:Name tt:VideoEncoderConfiguration ... /tt:VideoEncoderConfiguration /trt:Profiles /trt:GetProfilesResponse /SOAP-ENV:Body /SOAP-ENV:Envelope); return buf; }细看会发现这个响应本身就是一个函数化构造而不是模板拼接好处是后续要改分辨率或帧率时参数直接从全局配置读取不会出现Profile和实际流属性不一致的乌龙。3.3 设备管理、Media服务与核心接口实现设备管理部分的接口不多但每个都要准确。GetDeviceInformation返回的信息决定NVR界面上显示的名称、厂商和型号。我一开始随手填了个模型名结果某品牌NVR会用它做默认的主机名导致最终通道名显示乱码。后来我改成固定字段Manufacturer填自家产品代号Model填型号版本FirmwareVersion同步固件版本号SerialNumber用芯片MAC地址格式化后填入GetCapabilities要声明支持的ONVIF类型。我按照Profile S的约定把Media和Device两类能力都填上并给出相应的XAddr否则NVR后续会找不到Media服务地址。GetProfiles是NVR最关心的接口之一。Profile代表一路流媒体的配置集合里面包含视频编码配置、视频源配置和RTSP地址。我们做MJPEG流那VideoEncoderConfiguration里就要明确写成JPEG分辨率与帧率要和实际采集完全一致。如果Profile说支持1080p但实际出的是640x480流NVR可以拉到流但画面会异常。3.4 Digest认证与NVR访问控制很多NVR首次添加设备时会要求输入用户名密码。这个验证用的是ONVIF的HTTP Digest认证。如果设备不实现认证NVR可能允许匿名访问但为了更像正规IPC我还是加上了替代方案默认支持匿名同时保留Digest校验入口。Digest认证的核心是计算三次MD5值。NVR会先挑一个没有Authorization头的HTTP请求服务器响应401并携带nonce和realmNVR再用用户名密码算出摘要填回请求头。在MCU上做这件事不难ESP-IDF自带的mbedtls里就有MD5函数只是要注意nonce是每次请求变化的不能用静态值。我给的实现建议是设备端维护用户名密码的配置写在NVS里。每个受保护的URI在进入处理回调前先校验Authorization头。解析出username、realm、nonce、uri、response字段按RFC 2617公式计算期望值。比对一致后再放行到具体的ONVIF处理函数。认证一旦开启要特别小心GetSystemDateAndTime接口。Digest的nonce有时会带时间戳若设备时间和NVR相差太远校验会失败。最省心的方案是让设备在启动时通过DHCP或NTP自动校准时间并对GetSystemDateAndTime返回UTC格式的准确时间值。很多“设备连接不上”的怪问题根因就是时间没同步。4. RTSP与实时视频流让NVR“拉得走、播放得动”4.1 RTSP会话流程与状态机ONVIF只负责告诉NVR去哪拉流真正出画面还得靠RTSP。RTSP的流程是标准的四步OPTIONS、DESCRIBE、SETUP、PLAY。作为服务器ESP32上RTSP的实现需要维护每个会话的状态。我用一个简单结构体保存会话状态包括当前阶段INIT、READY、PLAYING、客户端端口号、以及RTP通道相关配置。请求进来时按状态机和请求方法跳转处理OPTIONS不管什么状态都回200并声明支持的RTSP方法列表。DESCRIBE返回SDP描述。SDP里要写明视频编码类型JPEGrtpmap以及控制URL。SETUP客户端会把RTP端口或interleaved通道告诉我们这时要记录传输模式。PLAY回复200同时启动一个发送JPEG帧到指定端口的循环任务。一个很常见的坑是RTSP URL的匹配。ONVIF的GetStreamUri返回的地址可能是rtsp://192.168.1.50:554/live0但NVR实际发起DESCRIBE时经常带的是绝对URI比如rtsp://192.168.1.50:554/live0/。如果服务器只允许精确匹配路径会被拒。我的处理是在路径匹配时去掉末尾斜杠并允许忽略查询字符串。4.2 MJPEG的RTP封装RFC 2435要点JPEG流在RTP里按RFC 2435封装比H.264的RTP封装简单不少。每个RTP包要带上JPEG的量化表和尺寸信息。MJPEG的一帧JPEG图片可能会被拆成多个RTP包或单独放进一个包。我采用的是“一帧一包”保证单包大小不超过MTU的典型值1400字节对于vga分辨率且quality10左右的JPEG帧多数情况下能塞进一个包。RTP头里要注意的关键字段是payload typeMJPEG一般为26。timestamp来自摄像头的帧间隔单位是90kHz时钟。我们摄像头输出固定帧率10fps所以时间戳增量是9000。在代码实现里发送一帧的流程大概是static void send_jpeg_frame(rtsp_session_t *session, stream_frame_t *frame) { static rtp_header_t rtp_hdr; rtp_hdr.timestamp 9000; rtp_hdr.sequence; rtp_hdr.ssrc session-ssrc; rtp_packet_t pkt; build_rtp_jpeg_packet(pkt, rtp_hdr, frame); udp_sendto(session-rtp_socket, pkt, pkt.len, session-rtp_dest); }RTP包发往NVR指定的端口而RTSP控制消息走TCP 554端口。这就意味着要在RTSP会话里同时维护一个UDP socket用于媒体传输。如果NVR使用TCP interleaved模式则需要把RTP包封装在$标记的TCP通道里走和RTSP相同的TCP连接。4.3 摄像头采集与JPEG帧缓冲管理摄像头采集部分我用的是经典的双缓冲策略。负责采集的任务从传感器驱动里取一帧JPEG数据写入buffer ARTSP发送任务则从buffer B读取并发送。采集完填充buffer A后自动交换两个buffer的指针。这样摄像头的采集帧率和网络发送速率互相独立不会因为网络抖动而把采集任务卡死。缓冲交换时要用临界区保护。cam_task从驱动拿到完整一帧后关中断交换指针rtsp任务发送前再开中断取走当前帧。这个实现虽然基础但在ESP32双核上表现稳定实测连续跑48小时没有出现内存碎片或丢帧导致的画面卡死。我还给采集任务设置了帧率控制。OV2640的输出帧率默认可能很高如果全速采集并发送NVR端反而会因为数据量过大而卡顿。我统一把输出帧率限制在10到15fps画质和带宽折中。对监控场景来说10fps已经足够看清动作。4.4 NVR兼容性取舍H.264还是MJPEG说到NVR显示必须坦诚市面上大部分NVR默认接收H.264或H.265流对MJPEG流的支持差异很大。有些NVR只要SDP里看到JPEG就拒绝添加有些则是能添加但画面卡顿。所以在项目启动前必须确认目标NVR是否支持MJPEG over RTSP否则ONVIF协议写得再完美最后一步也会卡壳。我这边实测的结论是多数使用ONVIF协议的软件录像机和一些小型NVR能识别MJPEG流但高端安防品牌为了标准化常默认只支持H.264。如果目标场景明确要H.264有三个升级路线用带硬件编码功能的ESP32-P4或外接视频编码芯片在服务器端转码换用Linux应用处理器方案从成本和开发量上看ESP32-P4这类带硬件编码能力的新芯片是更好的选择。ONVIF协议部分完全复用只需要把Profile里的编码类型改成H.264并在RTP打包时换成H.264的封包格式。5. 从组件到产品实机联调与NVR验证全流程5.1 开发环境准备与构建烧录工程基于ESP-IDF v5.x版本环境变量配好后直接进入工程目录构建idf.py set-target esp32s3 idf.py menuconfig idf.py build idf.py flash monitormenuconfig里要确认三处配置WiFi相关配套打开HTTP Server组件的最大URI handler数调大到20以上如果摄像头驱动作为独立组件还需开启PSRAM功能并把malloc策略改为优先使用PSRAM。摄像头帧缓冲建议放到PSRAM里内部SRAM留给协议栈和网络缓冲。构建完成后第一次烧录就启动设备。此时如果串口日志能看到“ONVIF service startedudp port 3702”“RTSP server listening on port 554”这类输出说明组件已经跑起来了。没有系统日志时也可以用电脑直接对3702端口发Probe看有没有回应。5.2 用通用ONVIF测试工具模拟NVR验证在接真实NVR之前强烈建议先用通用的ONVIF测试工具走一遍协议流程。这种工具通常能自动发送WS-Discovery Probe并列出发现到的设备也能手动调用GetDeviceInformation、GetProfiles、GetStreamUri等接口对调试协议响应非常有帮助。我用这类工具验证时的标准流程是点“Discover”确认设备出现在列表里选中设备查看厂商、型号、序列号打开Media接口列出Profiles获取StreamUri并确认可访问用内置播放器拉一次流确认画面正常。工具返回的任何错误我都会对照上一节的协议细节逐项排查。比如有一次测试工具报“Invalid Argument”错误追到日志里发现是GetProfilesResponse里漏了一个token属性NVR直接判定Profile不合法。5.3 真实NVR添加设备的操作流程测试工具通过之后再接真实NVR。我在某公司测试间用一台新装好的NVR做验证流程如下将NVR和ESP32接入同一台交换机确保同网段NVR的“添加设备”页面选择“ONVIF”协议点击搜索列表里出现开发板输入用户名密码或选匿名测试连接显示“在线”分配通道并完成添加预览画面确认实时流稳定。如果第3步就不出设备优先抓包看UDP 3702端口的Probe和ProbeMatch。NVR没收到响应问题大概率在XAddrs字段或设备没有正确绑定到多播地址。如果设备出现在列表但无法添加通常卡在后续的SOAP接口兼容性上需要打开串口日志看具体是哪个请求处理失败。在实时流阶段最值得留意的就是NVR预览窗口是否出现“网络错误”或“视频编解码错误”。前者说明RTSP会话建立失败后者说明SDP里描述参数跟实际流不一致。确保Profile里的resolution和帧率与实际发送一致能解决绝大多数画面问题。6. 问题排查与避坑实录6.1 发现阶段失败多播、网卡绑定、UUID我把在实测中遇到过的“发现失败”原因整理成一张排查表现象可能原因解决办法多播包发出去但设备无响应设备未加入多播组或网卡绑定错用setsockopt加入239.255.255.250并绑定到实际用到的网卡收到Probe但没回ProbeMatch类型匹配逻辑漏掉命名空间只检查本地标签名不做前缀硬匹配NVR显示设备存在但地址不可访问XAddrs填了不可路由的IP根据进入多播包的网卡IP动态生成XAddrs设备被搜到但名称乱码SerialNumber或Manufacturer含中文全部用ASCII序列号格式化为MAC地址还有一个容易被忽视的点UUID的稳定性。有些NVR会记录设备的UUID如果固件每次重启都生成新UUID设备会被重复添加。我建议把UUID固化在NVS里第一次启动时生成之后一直沿用。6.2 认证失败与时间同步Digest认证错误通常表现为NVR能搜到设备但添加时提示“用户名或密码错误”同时设备日志能看到401。排查时先确认三处密码是否和NVS配置一致nonce是否正确解析设备时间是否与NVR偏差太大。我在代码里加了调试日志每次认证失败会把期望摘要和收到的摘要都打印出来比对后能迅速定位是MD5计算问题还是nonce解析问题。另外一个经验是在开发初期把认证关掉等协议流程全部跑通之后再开启。否则每个接口的调试都会叠加一层404、401的判断排查问题效率很低。6.3 RTSP拉流失败URL、端口、传输模式RTSP阶段最常见的坑是“DESCRIBE的URL匹配失败”。NVR可能请求rtsp://ip:554/live0/而服务器只注册了/live0。我的处理是路径匹配时先移除末尾斜杠再忽略查询串。端口方面如果设备在防火墙后面或者NVR和ESP32跨VLANRTSP的554端口和RTP的动态端口可能无法打通。最简单的规避方案是让RTSP设置支持“TCP interleaved”模式即所有媒体数据都走TCP 554连接避免UDP端口映射的麻烦。大部分NVR在“传输协议”里都有“TCP”选项选TCP后兼容性最好。传输模式切换的本质是SETUP请求里的Transport头内容不同。UDP模式回应用户指定的RTP-AVP端口TCP模式则分配一个interleaved通道。两种模式我在代码里都支持了SETUP请求里怎么要求就怎么回。6.4 图像流不稳定帧率、缓冲、网络画面偶尔卡顿或花屏多半不是协议问题而是发送节奏不对。MJPEG一帧数据比较大如果发送任务和采集任务共用同一个队列网络拥堵时队列会堆积延迟越来越高。我的对策是采用“丢旧保新”策略队列满时直接丢弃最老的帧保证NVR看到的始终是最新画面。帧率控制也很重要。如果采集10fps但RTP时间戳按15fps累加NVR播放时会呈现“慢动作”且偶发缓冲不足。正确做法是让时间戳增量严格对应实际帧率10fps用9000增量15fps用6000增量。我同时用每秒统计实际发送帧数的辅助函数确保显示帧率与SDP描述一致。7. 个人经验与后续扩展7.1 这次开发中值得沉淀的做法几轮联调下来我最深的一条体会是写ONVIF设备端宁可少实现也不要返回“格式合法但参数错误”的内容。NVR对参数的一致性校验非常严格Profile里声明支持的分辨率必须出现在后续实际流里。与其规划一堆没实现的能力集不如在响应里只放一个Profile。另一点是要保持轻量的模块接口。我的组件对外只暴露了onvif_start()、onvif_stop()和几个配置参数所有协议逻辑都藏在内部。上层应用只需要关心要不要开启ONVIF以及设备当前处于联网状态还是离线状态这对长期维护非常有利。7.2 从MJPEG到H.264的升级路径如果你的产品最终要卖给对H.264有硬性要求的市场这套ONVIF-C组件不需要推翻重来。变动点集中在三个位置Profile里的VideoEncoderConfiguration编码类型改为H264RTSP的SDP中payload type改为96并补充profile-level-id参数RTP封装模块从RFC 2435替换为H.264的RTP单包封包逻辑。协议交互层、发现层、设备管理层完全复用。ESP32-P4这类具备硬件H.264编码能力的芯片出来后这套架构升级的成本比想象中低很多。7.3 可进一步扩展的方向在完整跑通“ONVIFRTSP”之后还可以继续补一些实用功能ONVIF Event服务向NVR推移动侦测事件云台控制PTZ接口配合电机驱动让NVR能远程转动镜头语音对讲通道在RTSP里增加音频流多Profile支持一路高清、一路流畅供NVR按需选择。这些功能的协议底层都是同一套SOAP/XML框架加接口时不用改整体架构只需在新的服务节点上增加对应的处理函数。这也是当初坚持组件化设计的最大红利。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进