• ONVIF学习笔记11:搜索设备不匹配问题排查


    上次移植的系统,编译加载都没问题,但搜索 onvif 设备失败了,经查,根本原因是 gsoap 版本不匹配导致。本文记录分析的过程。

    问题

    上半年进行一款飞腾板子的应用层系统移植,由于优先级不搞,加之有大量其它事务,断断续续地搞,至上个月终于有了阶段性结果,但遗留了一个问题。当时解决了封装的 onvif 相关的视频动态库编译和加载问题后,以为没问题了,测试发现 Qt 界面没有显示视频,分析日志,原来是视频动态库搜索不到设备。

    首先想到网络问题,经分析排除掉。接着抓包分析,能收到回应包。再跟踪源码,最终定位到 gsoap 版本不匹配问题。

    过程

    原问题重现

    其实之前解决加载问题本身就有问题,当时是其它部门提供 gsoap 库,但是是最新的2.8.122版本,因为找不到2.8.90版本,而直接连接新版本库链接时会报如下错误:

    /home/latelee/work/videolib-debug/onvif/CMySoap.c:72: undefined reference to `soap_new_REQUIRE_lib_v20890'
    
    • 1

    经分析,和版本号有关的代码片段如下:

    // stdsoap2.h
    #define GSOAP_VERSION 20890
    
    // soapStub.h
    #include "stdsoap2.h"
    #if GSOAP_VERSION != 20890
    # error "GSOAP VERSION 20890 MISMATCH IN GENERATED CODE VERSUS LIBRARY CODE: PLEASE REINSTALL PACKAGE"
    #endif
    
    // stdsoap2.h
    #define soap_versioning_paste(name, ext) name##_REQUIRE_lib_v##ext
    #define soap_versioning_ext(name, ext) soap_versioning_paste(name, ext)
    #define soap_versioning(name) soap_versioning_ext(name, GSOAP_VERSION)
    
    #define soap_init(soap) soap_init1(soap, SOAP_IO_DEFAULT)
    #define soap_init1(soap, mode) soap_init2(soap, mode, mode)
    #define soap_init2(soap, imode, omode) soap_versioning(soap_init)(soap, imode, omode)
    
    #define soap_new() soap_new1(SOAP_IO_DEFAULT)
    #define soap_new1(mode) soap_new2(mode, mode)
    #define soap_new2(imode, omode) soap_versioning(soap_new)(imode, omode)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    可以看到,在调用soap_newsoap_init时,会生成和版本号有关的函数,对于 2.9.90 版本,是soap_new_REQUIRE_lib_v20890,而2.9.122,则是soap_new_REQUIRE_lib_v208122。为解决链接问题,将代码中的版本号宏定义改为最新的版本号,如此一来,加载正常。

    相机端确认

    因为相机并不是生产环境使用的,因此需要首先确认相机是否能正常提供 onvif 功能。将相机与电脑直连,修改IP,再找到当年测试 onvif 的工具 odtt,安装不了,但 odm 能安装成功,打开搜索,搜索不到,手动指定 IP,可找到,观察视频也正常。获取到的地址为:

    http://192.168.18.168:80/onvif/device_service
    
    • 1

    设备上抓包

    相机端确认正常后,接着怀疑是网络问题,因为板子上有2个网关,测试时,2个网段都在工作。为保证网络环境纯粹性,禁用另一网关,并设置默认环境,板子系统上已有tcpdump命令,用如下命令抓包:

    tcpdump -i eth0 -w result.cap
    
    • 1

    将抓包文件用 scp 传输到本地,用 wireshark 工具分析,发现搜索包和回应包都有,xml 内容也是正常的。

    发送包如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsdd="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:chan="http://schemas.microsoft.com/ws/2005/02/duplex" xmlns:wsa5="http://www.w3.org/2005/08/addressing" xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml1="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:wsc="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xmime="http://tempuri.org/xmime.xsd" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:tt="http://www.onvif.org/ver10/schema" xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" xmlns:tdn="http://www.onvif.org/ver10/network/wsdl" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" xmlns:trt="http://www.onvif.org/ver10/media/wsdl"><SOAP-ENV:Header><wsa:MessageID/><wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><wsdd:Probe><wsdd:Types/><wsdd:Scopes/></wsdd:Probe></SOAP-ENV:Body></SOAP-ENV:Envelope>
    
    • 1
    • 2

    回应包:

    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope
        xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
        xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#"
        xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
        xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
        xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
        xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
        xmlns:wsadis="http://schemas.xmlsoap.org/ws/2004/08/addressing"
        xmlns:wsrp="http://schemas.xmlsoap.org/rp/"
        xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
        xmlns:wsa5="http://www.w3.org/2005/08/addressing"
        xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
        xmlns:xop="http://www.w3.org/2004/08/xop/include"
        xmlns:ter="http://www.onvif.org/ver10/error"
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        xmlns:soap12="http://tempuri.org/soap12.xsd"
        xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
        xmlns:tnsn="http://www.eventextension.com/2011/event/topics"
        xmlns:tt="http://www.onvif.org/ver10/schema"
        xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2"
        xmlns:wstop="http://docs.oasis-open.org/wsn/t-1"
        xmlns:tns1="http://www.onvif.org/ver10/topics"
        xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2"
        xmlns:dndl="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding"
        xmlns:dnrd="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding"
        xmlns:dn="http://www.onvif.org/ver10/network/wsdl"
        xmlns:tds="http://www.onvif.org/ver10/device/wsdl"
        xmlns:tevcp="http://www.onvif.org/ver10/events/wsdl/CreatePullPointBinding"
        xmlns:teve="http://www.onvif.org/ver10/events/wsdl/EventBinding"
        xmlns:tevp="http://www.onvif.org/ver10/events/wsdl/PullPointBinding"
        xmlns:tev="http://www.onvif.org/ver10/events/wsdl"
        xmlns:tevps="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding"
        xmlns:tevpsm="http://www.onvif.org/ver10/events/wsdl/PausableSubscriptionManagerBinding"
        xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"
        xmlns:tevsm="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding"
        xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl"
        xmlns:timg10="http://www.onvif.org/ver10/imaging/wsdl"
        xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl"
        xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl"
        xmlns:tptz10="http://www.onvif.org/ver10/ptz/wsdl"
        xmlns:tr2="http://www.onvif.org/ver20/media/wsdl"
        xmlns:trc="http://www.onvif.org/ver10/recording/wsdl"
        xmlns:trp="http://www.onvif.org/ver10/replay/wsdl"
        xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
        xmlns:tse="http://www.onvif.org/ver10/search/wsdl">
        <SOAP-ENV:Header>
            <wsadis:MessageID>urn:uuid:5555f550-5535-35f6-ec55-952cbd3d45191</wsadis:MessageID>
            <wsadis:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsadis:To>
            <wsadis:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsadis:Action>
            <d:AppSequence InstanceId="0" MessageNumber="10"></d:AppSequence>
        </SOAP-ENV:Header>
        <SOAP-ENV:Body>
            <d:ProbeMatches>
                <d:ProbeMatch>
                    <wsadis:EndpointReference>
                        <wsadis:Address>urn:uuid:5555f550-5535-35f6-ec55-952cbd3d45191</wsadis:Address>
                    </wsadis:EndpointReference>
                    <d:Types>dn:NetworkVideoTransmitter tds:Device</d:Types>
                    <d:Scopes>onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/type/ptz onvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/location/city/hangzhou onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/Profile/G onvif://www.onvif.org/Profile/T onvif://www.onvif.org/hardware/D2150-10-SIU onvif://www.onvif.org/name/D2150-10-SIU </d:Scopes>
                    <d:XAddrs>http://192.168.18.168:80/onvif/device_service</d:XAddrs>
                    <d:MetadataVersion>1</d:MetadataVersion>
                </d:ProbeMatch>
            </d:ProbeMatches>
        </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    调试源代码

    外部因素排除外,只能跟踪源码了——这是我最不想做的事。由于代码原本为动态库,首先改造为可执行程序,为了兼容动态库,使用自编的 Makefile,再分析 eclipse 工程,添加涉及的库、头文件路径,编译通过后执行,在搜索设备的soap_recv___wsdd__ProbeMatches函数出错,返回3。跟踪该函数代码:

    SOAP_FMAC5 int SOAP_FMAC6 soap_recv___wsdd__ProbeMatches(struct soap *soap, struct __wsdd__ProbeMatches *_param_1)
    {
    	soap_default___wsdd__ProbeMatches(soap, _param_1);
    	soap_begin(soap);
    	int ret = 0;
    	ret = soap_begin_recv(soap);
    if (ret) {printf("!!! %d return. %d soap err: %d\n", __LINE__, ret, soap->error); return ret;}
    	ret = soap_envelope_begin_in(soap);
    if (ret) {printf("!!! %d return. %d soap err: %d\n", __LINE__, ret, soap->error); return ret;}
    	ret = soap_recv_header(soap);
    if (ret) {printf("!!! %d return. %d soap err: %d\n", __LINE__, ret, soap->error); return ret;}
    	ret = soap_body_begin_in(soap);
    	if (ret) {printf("!!! %d return. %d soap err: %d\n", __LINE__, ret, soap->error); return ret;}
    	ret = soap_closesock(soap);
    	if (ret) {printf("!!! %d return. %d soap err: %d\n", __LINE__, ret, soap->error); return ret;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    发现在soap_body_begin_in函数出错,返回值 3,宏定义为SOAP_TAG_MISMATCH,相关定义如下:

    // stdsoap2.h
    #define SOAP_EOF                        EOF
    #define SOAP_OK                         0
    #define SOAP_CLI_FAULT                  1
    #define SOAP_SVR_FAULT                  2
    #define SOAP_TAG_MISMATCH               3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    soap_body_begin_in函数定义如下:

    soap_body_begin_in(struct soap *soap)
    {
      if (soap->version == 0)
        return SOAP_OK;
      soap->part = SOAP_IN_BODY;
      if (soap_element_begin_in(soap, "SOAP-ENV:Body", 0, NULL))
        return soap->error;
      if (!soap->body)
        soap->part = SOAP_NO_BODY;
      return SOAP_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    跟踪soap_element_begin_in函数,发现只要soap结构体的other字段为1,即返回SOAP_TAG_MISMATCH

    soap_element_begin_in(struct soap *soap, const char *tag, int nillable, const char *type)
    {
      if (!soap_peek_element(soap))
      {
        if (soap->other)
          return soap->error = SOAP_TAG_MISMATCH;
        if (tag && *tag == '-')
          return SOAP_OK;
        ...
      }
      return soap->error;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    跟踪soap_peek_element函数,和other字段有关的代码如下:

    soap_peek_element(struct soap *soap)
    {
      soap->other = 0;
        {
            else if (!soap_match_tag(soap, tp->name, "SOAP-ENV:actor"))
            {
              if ((!soap->actor || strcmp(soap->actor, tp->value))
               && strcmp(tp->value, "http://schemas.xmlsoap.org/soap/actor/next"))
                soap->other = 1;
            }
          }
          else if (soap->version == 2)
          {
    #ifndef WITH_NOIDREF
            if (!soap_match_tag(soap, tp->name, "SOAP-ENC:id"))
            {
            }
            else
    #endif
            if (!soap_match_tag(soap, tp->name, "SOAP-ENC:itemType"))
            {
            }
            else if (!soap_match_tag(soap, tp->name, "SOAP-ENV:role"))
            {
              if ((!soap->actor || strcmp(soap->actor, tp->value))
               && strcmp(tp->value, "http://www.w3.org/2003/05/soap-envelope/role/next"))
                soap->other = 1;
            }
          }
          else
          {
          }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    大意是,如果tag匹配了SOAP-ENV:actorSOAP-ENV:role,但某些值却不匹配,就设置other为1,最终返回不匹配错误。

    分析对比了 2.8.90版本源码,该函数的判断部分没有改动,至于为什么旧版本可以,暂未知。

    为了一探究竟,将相关函数移动到测试代码中,但牵扯到函数太多,最终发现soap结构体字段不相同——特别是有个在使用的函数指针是新版本才有的,于是放弃此路。

    其实,到此时已经明确知道了就是 gsoap库版本问题,于是只能继续找原版本的源码来编译了。

    重新编译库

    在网上搜索 gsoap,很少能找到官方的版本,默认提供的是最新版本的 gsoap,但皇天不负有心人,还是找到官方的 svn 仓库https://sourceforge.net/p/gsoap2/code/177/log/?path=,相关信息如下:

    [r172] by  engelen  2019-08-14 13:32:42
    gSOAP 2.8.90 stable
    
    • 1
    • 2

    源码只能用 svn 下载:

    https://sourceforge.net/p/gsoap2/code/HEAD/tree/
    
    • 1

    下载后,切换到 172 分支,导出,打包,传输到板子系统上进行编译。

    首先安装依赖包:

    sudo yum install autoconf automake flex bison openssl-devel zlib-devel -y
    
    • 1

    配置编译gsoap:

    ./configure prefix=/home/latelee/tools/soap && make && make install
    
    • 1

    提示aclocal命令找不到:

    CDPATH="${ZSH_VERSION+.}:" && cd . && /bin/sh /home/latelee/work/gsoap-2.8.90/missing aclocal-1.16 
    /home/latelee/work/gsoap-2.8.90/missing: line 81: aclocal-1.16: command not found
    WARNING: 'aclocal-1.16' is missing on your system.
    
    • 1
    • 2
    • 3

    使用 yum 安装最高只有 1.13 版本:

    $ /usr/bin/aclocal --version
    aclocal (GNU automake) 1.13.4
    
    • 1
    • 2

    根据错误提示安装1.16版本:

    wget http://ftp.gnu.org/gnu/automake/automake-1.16.1.tar.gz
    tar -xf automake-1.16.1.tar.gz
    cd automake-1.16.1/
    ./configure
    make
    sudo make install
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再次编译安装:

    ./configure prefix=/home/latelee/tools/soap && make && make install
    
    • 1

    得到如下文件:

    libgsoap.a    libgsoapck.a    libgsoapssl.a    pkgconfig
    libgsoap++.a  libgsoapck++.a  libgsoapssl++.a
    
    • 1
    • 2

    视频动态库使用了 gsoap 库(具体名称为libgsoapssl.so),但默认编译没有动态库,为了减少库的依赖,在 Makefile 中指定静态库文件libgsoapssl.a,但是编译出错:

    Generating dynamic lib file... libvideolib.so
    /usr/bin/ld: /home/latelee/work/videolib-debug/soap/lib/libgsoapssl.a(libgsoapssl_a-stdsoap2_ssl.o): relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `GENERAL_NAME_free@@libcrypto.so.10' can not be used when making a shared object; recompile with -fPIC
    /usr/bin/ld: /home/latelee/work/videolib-debug/soap/lib/libgsoapssl.a(libgsoapssl_a-stdsoap2_ssl.o)(.text+0x1ff68): unresolvable R_AARCH64_ADR_PREL_PG_HI21 relocation against symbol `GENERAL_NAME_free@@libcrypto.so.10'
    /usr/bin/ld: final link failed: Bad value
    collect2: error: ld returned 1 exit status
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不知何故无法链接,只能按原来的做法,重新编译 gsoap,指定动态库:

    ./configure prefix=/home/latelee/tools/soap \
    –-enable-shared
    
    • 1
    • 2

    但失败,不支持动态库的生成:

    checking build system type... Invalid configuration `–-enable-shared': machine `–-enable' not recognized
    configure: error: /bin/sh ./config.sub –-enable-shared failed
    
    • 1
    • 2

    想着改编译脚本生成,但太复杂了,于是根据编译过程的输出内容,从生成静态库的相关命令找到目标文件,再重新链接成动态库,手动生成命令如下:

    gcc -shared -fPIC -o libgsoapssl.so libgsoapssl_a-stdsoap2_ssl.o libgsoapssl_a-dom.o
    
    • 1

    虽然有警告,但能成功生成libgsoapssl.so文件,

    /usr/bin/ld: libgsoapssl_a-stdsoap2_ssl.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `namespaces' can not be used when making a shared object; recompile with -fPIC
    /usr/bin/ld: libgsoapssl_a-stdsoap2_ssl.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `GENERAL_NAME_free' can not be used when making a shared object; recompile with -fPIC
    /usr/bin/ld: libgsoapssl_a-stdsoap2_ssl.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `X509V3_conf_free' can not be used when making a shared object; recompile with -fPIC
    /usr/bin/ld: libgsoapssl_a-stdsoap2_ssl.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `X509V3_conf_free' can not be used when making a shared object; recompile with -fPIC
    
    • 1
    • 2
    • 3
    • 4

    根据经验分析,代码中没有使用有与X509有关的函数,后面是否使用,届时再看。

    得到动态库,再编译就正常了。测试程序也能搜索到设备了。

    其它

    根据以往的经验,记录一下不同相机的rtsp地址(仅个人经验):

    海康摄像机:
    rtsp://172.18.203.44:554/Streaming/Channels/1?transportmode=unicast&profile=Profile_1
    
    华为相机:
    rtsp://192.168.18.168:554/LiveMedia/ch1/Media1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在测试时发现,必须设置默认网关才能正常搜索到设备,否则,哪怕指定了静态路由也不行。因为在实验环境中才有双网络,因此该问题没有深入研究。

    小结

    onvif 的框架代码,经典者如soapClient.csoapC.c等,是用 gsoap 根据 onvif 的 wsdl 文件生成的,一旦使用了某个 gsoap 版本的代码,就定型了,如果更换,则必须重新生成框架代码。

    代码可以再优化一下,由于问题卡在设备搜索,可以在配置文件中添加选项,可选择跳过搜索步骤,直接连接设备,再根据 xml 分析出 rtsp 地址。但是因为时间原因,而且面对的是传承3年的代码,修改有一定困难。

    后记

    视频库用的 gsoap 版本是 2019 年8月14日发布的,距今近3年了。

    回查工作手账,其后的一天,重构后的二期充电桩系统平稳上线;其后的一个月,因一直未发工资,内部讨论如何有序安全离职;再其后的一个月,正式提交辞呈。

    而 onvif 系列文章,其前4月写了上一篇,其3年前则是前一篇。

    如今又搞 onvif,天意何时何地都在。

  • 相关阅读:
    63页PPT丁玉婕事件下载查看攻略
    #分支语句详解
    解决:将Ubuntu系统打包成ios镜像并制作U盘系统
    Linux 应用---make及makefile的编写
    第3章 Linux网络编程 01. 网络结构模式
    答应我从这篇文章开始你的C语言之旅吧
    ESP32上电到app_main()的过程梳理
    px转rem插件postcss-plugin-px2rem使用方法(浏览器缩放页面自适应)
    基于ssm的高校阅读分享推荐系统
    如果一个集合的Lebesgue测度为0, 那么它的自己也是Lebesgue可测的并且其测度也为0.
  • 原文地址:https://blog.csdn.net/subfate/article/details/125610910