技术解码 | GB28181/SIP/SDP 协议--EasyGBS国标GB28181平台国标视频技术GB28181解析

视频流媒体安防监控国标GB28181平台EasyGBS视频能力丰富,部署灵活,既能作为业务平台使用,也能作为安防监控视频能力层被业务管理平台调用。国标GB28181视频监控EasyGBS平台可提供流媒体接入、处理、转发等服务,支持内网、公网的安防视频监控设备通过国标GB/T28181协议进行视频监控直播等功能。

今天我们就来了解一下EasyGBS国标GB28181视频平台中关于GB28181的解析。

GB28181协议指的是国家标准GB/T 28181—2016《公共安全视频监控联网系统信息传输、交换、控制技术要求》。

该标准规定了公共安全视频监控联网系统的互联结构, 传输、交换、控制的基本要求和安全性要求, 以及控制、传输流程和协议接口等技术要求,是视频监控领域的国家标准。GB28181协议信令层面使用的是SIP(Session Initiation Protocol)协议。

流媒体传输层面使用的是实时传输协议(Real-time Transport Protocol,RTP)协议。

因此可以理解为GB28181是在国际通用标准的基础之上进行了私有化定制以满足视频监控联网系统互联传输的标准化需求。本文旨在说明在FFmpeg中增加对GB28181协议的支持,使其可以与支持GB28181协议的设备进行通信与控制,实现设备的注册、保活以及流媒体的传输。
 

国标GB28181协议:信令流程

国标GB28181协议

GB28181协议的会话通道实际上是基于SIP协议,并在此基础上进行了定制化处理。SIP是由IETF MMUSIC工作组开发的协议,旨在标准化创建、修改和终止交互式用户会话,涵盖视频、语音、即时通信、在线游戏和虚拟现实等多媒体元素。

在SIP中,用户代理(User Agent)是一个关键概念,它指的是一个SIP逻辑网络端点,用于创建、发送、接收SIP消息并管理SIP会话。用户代理可以作为客户端(User Agent Client,UAC)发送请求,也可以作为服务器(User Agent Server,UAS)接收和处理请求。通过用户代理,SIP实现了用户之间的通信和会话控制。

SIP用户代理可以分为用户代理客户端(User Agent Client,UAC)和用户代理服务端(User Agent Server,UAS)。UAC负责创建并发送SIP请求,而UAS则负责接收和处理SIP请求,并发送相应的SIP响应。这种分工使得SIP协议能够实现双方之间的会话控制和通信。

SIP协议通常与许多其他协议协同工作。其中一个重要的协议是会话描述协议(Session Description Protocol,SDP),它用于发送SIP报文的内容。SDP协议描述了会话所使用的流媒体细节,例如使用的IP地址和端口号,采用的编解码器等。通过SDP协议,SIP能够确保会话中的参与方能够理解和适应正确的媒体流参数,从而实现有效的通信。

SIP的一个典型用途是传输经过报文的实时传输协议流。在这种情况下,RTP(Real-time Transport Protocol)扮演了语音或视频的载体角色。在GB28181协议中,联网系统用于视音频传输及控制需要建立两个通道:会话通道和媒体流通道。会话通道用于在设备之间建立会话并传输系统控制命令;而媒体流通道则用于传输经过压缩编码的视音频数据,其中流媒体协议RTP/RTCP被用作媒体流通道的传输协议。通过这种方式,GB28181协议能够有效地传输视音频数据并进行系统控制。GB28181协议中具体通信协议结构图如下图(通信协议结构图)所示:

注册:

在GB28181中,设备通过向SIP服务器发送REGISTER请求来注册自己。REGISTER请求的消息结构与SIP协议IETF RFC3261中规定的相同,包括请求行、头部和消息体。其中请求行指定了方法(REGISTER)、URI和SIP版本;头部包括了SIP地址、认证信息等等;消息体为空。

保活:

为了保持会话通道的活性,设备需要定期发送SIP消息来告知SIP服务器它仍然存在。这种SIP消息称为保活消息,采用OPTIONS方法实现。保活消息的消息结构与SIP协议IETF RFC3261中规定的相同,包括请求行、头部和消息体。其中请求行指定了方法(OPTIONS)、URI和SIP版本;头部包括了SIP地址、认证信息等等;消息体为空。

实时视音频点播:

当前端用户需要实时查看视频时,首先需要向摄像头设备发送INVITE请求,邀请设备开始传输视频流。INVITE请求的消息结构与SIP协议IETF RFC3261中规定的相同,包括请求行、头部和消息体。其中请求行指定了方法(INVITE)、URI和SIP版本;头部包括了SIP地址、认证信息、编解码器等等;消息体为空。设备回复INVITE请求时使用200 OK响应,其消息结构与SIP协议IETF RFC3261中规定的相同,包括状态行、头部和消息体。其中状态行指定了SIP版本和状态码(200 OK),头部包括了SIP地址、编解码器等等;消息体含有SDP协议格式的媒体流参数。

国标28181:注册

注册指的是设备或系统进入联网系统时向SIP服务器(SIP UAS)进行注册登记的工作模式,在本文中FFmpeg即为一个SIP服务器,设备向FFmpeg发送注册请求,FFmpeg在接收到设备的注册请求后返回相应的回复消息,则完成设备注册流程。GB28181协议中基于数字摘要的挑战应答式安全技术进行注册流程如下图所示:

注册流程描述如下:

SIP代理向SIP服务器发送Register请求;

SIP服务器向SIP代理发送响应401,并在响应的消息头WWW_Authenticate字段中给出适合SIP代理的认证体制和参数;

SIP代理重新向SIP服务器发送REGISTER请求, 在请求的Authorization字段给出信任书,包含认证信息;

SIP服务器对请求进行验证,如果检查出SIP代理身份合法,向SIP代理发送成功响应200OK,如果身份不合法则发送拒绝服务应答。

注册的请求消息内容范例如下:

REGISTER sip:34020000002000000001@3402000000 SIP/2.0
Via: SIP/2.0/UDP 192.168.137.11:5060;rport;branch=z9hG4bK1371463273
From: sip:34020000001320000003@3402000000;tag=2043466181
To: sip:34020000001320000003@3402000000
Call-ID: 1011047669
CSeq: 1 REGISTER
Contact: sip:34020000001320000003@192.168.137.11:5060
Max-Forwards: 70
User-Agent: IP Camera
Expires: 3600
Content-Length: 0

注册请求头域参数解释如下:

第1行表明这条SIP消息的方法(Method)是REGISTER,34020000002000000001是SIP服务器的国标ID,国标ID指的是由中心编码(8位) 、行业编码(2位) 、类型编码(3位)和序号(7位)四个码段共20位十进制数字字符构成,具体国标ID的编码方法可以参考GB/T 28181—2016中的附录D。3402000000指的是SIP服务器的域国标ID,SIP/2.0指的是SIP协议版本。 
第2行为Via头,Via头中包含了发送请求方的相关信息,后续需要使用这些信息进行回复。SIP/2.0/UDP表示使用的是2.0版本的SIP协议,使用的传输协议是UDP,也可以使用TCP协议。192.168.137.11:5060为请求发送方的IP地址和端口号。Via头中必须包含branch参数,具体值是一个在整个SIP通信过程中不重复的数值。branch是一个事务ID(Transaction ID),用于区分同一个UA所发起的不同Transaction,它不会对未来的request或者是response造成影响,对于遵循IETF RFC3261规范的实现,这个branch参数的值必须用”z9hG4bK”打头. 其它部分是对To, From, Call-ID头域和Request-URI按一定的算法加密后得到。rport字段表示使用rport机制路由响应,即发送的响应时,按照rport中的端口发送SIP响应,也就是说IP和端口均完全遵照从哪里来的,发回哪里去的原则,如果没有rport字段时,服务端的策略是IP使用UDP包中的地址,即从哪里来回哪里去,但是端口使用的是via中的端口,详情见IETF RFC35818。  
第3行为From头,From头中包含了请求发送方的逻辑标识,在GB28181协议中是发送请求的设备国标ID和域国标ID信息。tag参数是为了身份认证的,值为随机数字字符。  
第4行为To头,To头在SIP协议中是为了标明请求接收方的逻辑标识的,在GB28181协议中填写的是发送请求的设备国标ID和域国标ID信息。  
第5行为Call-ID头,Call-ID头是全局唯一的,在同一个session中保持一致,在不同session中不同。  
第6行为CSeq头,CSeq头又叫Command Seqence(命令队列),用于标识命令顺序,值为序号+Method,序号部分为无符号整数,最大值为2^31。序号起始值是随机的,后续在同一个session中依次递增,比如发1 REGISTER没返回--->再发2 REGISTER--->没返回--->再发3 REGISTER--->这时返回了2 REGISTER就知道是第2个请求得到了响应。对于ACK和CANCLE中的CSeq与INVITE中的Cseq保持一致。  
第7行为Contact头,Contact头包含源的URI信息,用来给响应消息直接和源建立连接用。在GB28181协议中为SIP设备编码@源IP地址端口。  
第8行为Max-Forwards头,Max-Forwards头用于设置包最大中转次数,默认是70。  
第9行为User-Agent头,User-Agent头用于设置关于UA的信息,用户可以自定义。  
第10行为Expires头,Expires头表示超时时间。  
第11行为Content-Length头,Content-Length头表示SDP消息的长度,因为REGISTER消息不需要SDP,因此为0。

注册的回复消息内容范例如下,各头信息含义见上面:

SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.137.11:5060;rport;branch=z9hG4bK1371463273
From: sip:34020000001320000003@3402000000
To: sip:34020000001320000003@3402000000
CSeq: 1 REGISTER
Call-ID: 1011047669
Contact: sip:34020000001320000003@192.168.137.11:5060
User-Agent: FFmpeg GB28181 v1.0
Expires: 3600
Content-Length: 0

国标28181:保活

当用户代理(UA)发现自身工作异常时,应立即向所属的SIP监控域的SIP服务器发送状态信息。同样地,当工作正常时,也应定时向所属的SIP监控域的SIP服务器发送状态信息。为了实现这一功能,可以使用IETF RFC3427中定义的MESSAGE方法来进行状态信息的报送。
通过周期性的状态信息报送,可以实现注册服务器与源设备之间的状态检测,也被称为心跳机制。当UA向SIP服务器发送状态信息时,服务器可以根据接收到的消息判断设备的工作状态,如是否在线、是否正常运行等。如果服务器长时间未收到状态信息,就可以判断该设备可能存在故障或不可用的情况。

使用MESSAGE方法进行状态信息的报送时,需要构建相应的SIP消息体,并在其中包含相关的状态信息。具体的报送频率和内容可以根据实际需求和协议规定进行配置。

构建SIP消息体时,需要按照SIP协议(IETF RFC3261)的规范进行编码。消息体可以使用SIP消息头和消息体来传递状态信息。在SIP消息头中,可以添加自定义的头部字段或使用预定义的头部字段,如Subject、Content-Type等,以表明消息的目的和内容类型。在消息体中,可以携带自定义的状态信息,例如设备状态、错误码、工作参数等。

报送频率是根据实际需求和协议规定来确定的。可以根据监控要求和系统性能考虑,设置适当的报送频率,以满足及时监测设备状态的需要。一般而言,报送频率可以由监控域的SIP服务器和源设备之间的约定来确定。

报送的内容也是根据实际情况和协议规定来确定的。具体可以包括设备的健康状态、连接质量、报警信息等。根据需求,可以定义自定义的消息格式和字段,以满足特定的监测要求。

总之,通过使用MESSAGE方法进行状态信息的报送时,需要构建相应的SIP消息体,并在其中包含相关的状态信息。具体的报送频率和内容需要根据实际需求和协议规定进行配置,以满足监测要求和系统性能。通过实现心跳机制,可以及时监测设备的状态并采取相应的处理措施,提高系统的可靠性和稳定性。

心跳发送方、接收方需统一配置“心跳间隔”参数,按照“心跳间隔”定时发送心跳消息,默认心跳间隔60s。心跳发送方、接收方需统一配置“心跳超时次数”参数,心跳消息连续超时达到“心跳超时次数”则认为对方下线,默认心跳超时次数3次。心跳接收方在心跳发送方上线状态下检测到心跳消息连续超时达到商定次数则认为心跳发送方离线; 心跳发送方在心跳接收方上线状态下检测到心跳消息响应消息连续超时达到商定次数则认为心跳接收方离线。具体命令流程如下图:

命令流程描述如下:

(a) 源设备向SIP服务器发送设备状态信息报送命令。设备状态信息报送命令采用MESSAGE方法携带;

(b) SIP服务器收到命令后返回200 OK。

保活消息内容范例如下:

MESSAGE sip:34020000002000000001@3402000000 SIP/2.0
Via: SIP/2.0/UDP 192.168.137.11:5060;rport;branch=z9hG4bK1066375804
From: sip:34020000001320000003@3402000000;tag=1925919231
To: sip:34020000002000000001@3402000000
Call-ID: 1185236415
CSeq: 20 MESSAGE
Content-Type: Application/MANSCDP+xml
Max-Forwards: 70
User-Agent: IP Camera
Content-Length: 175
 
<?xml version="1.0" encoding="UTF-8"?>
<Notify>
<CmdType>Keepalive
<SN>1
<DeviceID>34020000001320000003
<Status>OK
<Info>
</Info>
</Notify>

Message回复消息内容范例如下:

SIP/2.0 200 OK
 
Via: SIP/2.0/UDP 192.168.137.11:5060;rport;branch=z9hG4bK1066375804
 
From: sip:34020000001320000003@3402000000
 
To: sip:34020000002000000001@3402000000
 
CSeq: 20 MESSAGE
 
Call-ID: 1185236415
 
User-Agent: FFmpeg GB28181 v1.0
 
Content-Length: 0

国标28181:实时视频播放

视频流格式

GB28181要求传输的视频流格式为PS流,或者H264流,或者MP4格式。
可以用wireshark抓包,数据报类型是RTP的PS流

国标流媒体服务器其实就是负责将GB28181设备或者平台推送的PS流转成ES流,然后提供RTSP、RTMP、FLV、HLS等格式进?分发。

PS流和ES流的区别

P数据报有首部和数据两部分组成的,首部的前⼀部

分是固定长度20字节,是所有IP数据报必须具有的。首部包括:总长度、标识、MF、DF、片偏移。

数字信号实际传送的是数据流,一般数据流包括以下三种:

ES流(Elementary Stream):也叫基本码流,包含视频、音频或数据的连续码流。

PES流(Packet Elementary Stream):也叫打包的基本码流,是将基本的码流ES流根据需要分成长度不等的数据包,并加上包头就形成了打包的基本码流PES流。

TS流:也叫传输流,是由固定长度为188字节的包组成,含有独立时基的一个或多个program, 一个program也可以包含多个视频、音频、和文字信息的ES流; 每个ES流会有不同的PID标示,为了可以分析这些ES流, TS有一些固定的PID用来间隔发送program和ES流信息的表格: PAT和PMT表。适用于误码较多的环境

ES 是直接从编码器出来的数据流,可以是编码过的视频数据流,音频数据流,或其他编码数据流的统称。 ES 流经过PES 打包器之后,被转换成 PES 包,再通过RTSP、RTMP、FLV、HLS格式分发出去,实现WEB、手机、PC、微信等多终端的播。

传播方式

GBT28181协议规定码流使用RTP包负载,推荐为PS流,也可以是ES流,对于媒体流的传输在原有UDP传输的基础中,增加了主动tcp和被动tcp的方式。

UDP被动

这个是普遍的传输方式。GB28181流媒体服务器监听单个UDP端口,然后发送一个SIP信令(INVITE),其携带的SDP中包含了接收媒体的端口设备端收到信令后,解析该端口,然后设备主动通过UDP向流媒体服务端监听的那个端口上

送视频流

TCP被动

有两种,一种是主动,一种是被动

对于主动: 设备端告知服务端自己的媒体流tcp端口,服务端主动去连接设备端的该端口,获取数据。。这种场景应用较少,可以忽略

对于被动:流媒体服务器监听单个TCP端口,然后通过SIP信令(INVITE)告诉设备端口,设备主动向当前流媒体服务端发送视频流,基本等同于UDP流。

实时视频流程

前提:注册成功>>>>>>心跳成功>>>>>>设备目录查询>>>>>实时视频观看

服务端步骤

不管是TCP方式看,还是UDP方式看,其步骤都为:

(1)打开视频端口

(2)发送实时视频请求

(3)等待设备回复200OK

(4)发送ACK

(5)播放码流

(6)停止视频请求

(7)关闭视频端口

(8)普通等待

抓包分析

测试设备IP:(海康设备)

服务端IP:(本地ip)

实时视频建立_UDP

第零步:【服务端】打开视频端口

第一步:【服务端>>客户端】请求播放视频
 

INVITE sip:34020000001310000002@4401020049 SIP/2.0
 
Call-ID: helloVideo
 
CSeq: 1 INVITE
 
From: <sip:44010200492000000001@4401020049>;tag=bccedfd0111
 
To: <sip:34020000001110000001@4401020049>
 
Max-Forwards: 70
 
Contact: <sip:34020000001310000002@4401020049>
 
Via: SIP/2.0/UDP 192.168.0.107:5060;branch=z9hG4bKee5c5d98-bff9-4f3000002
 
Content-Type: application/sdp
 
Content-Length: 225v=0
 
o=34020000001310000002 0 0 IN IP4 192.168.0.60
 
s=Play
 
c=IN IP4 192.168.0.60
 
t=0 0
 
m=video 6000 RTP/AVP 96 98 97
 
a=recvonly
 
a=rtpmap:96 PS/90000
 
a=rtpmap:98 H264/90000
 
a=rtpmap:97 MPEG4/90000
 
y=0100000001
 
f=

第二步:【客户端>>服务端】

先回复100

SIP/2.0 100 Trying
 
Via: SIP/2.0/UDP 192.168.0.107:5060;branch=z9hG4bKee5c5d98-bff9-4f3000002
 
From: <sip:44010200492000000001@4401020049>;tag=bccedfd0111
 
To: <sip:34020000001110000001@4401020049>;tag=5f906952
 
Call-ID: helloVideo
 
CSeq: 1 INVITE
 
Server: Happytime Agent Ver 1.0
 
Content-Length: 0
 
再回复200
 
SIP/2.0 200 OK
 
Via: SIP/2.0/UDP 192.168.0.107:5060;branch=z9hG4bKee5c5d98-bff9-4f3000002
 
From: <sip:44010200492000000001@4401020049>;tag=bccedfd0111
 
To: <sip:34020000001110000001@4401020049>;tag=5f906952
 
Contact: <sip:34020000001110000001@4401020049>
 
Call-ID: helloVideo
 
CSeq: 1 INVITE
 
Max-Forwards: 70
 
Allow: ACK,BYE,CANCEL,INVITE,NOTIFY,REFER,UPDATE,INFO
 
Supported: timer
 
Session-Expires: 200;refresher=uac
 
Server: Happytime Agent Ver 1.0
 
Content-Type: application/sdp
 
Content-Length: 153v=0
 
o=34020000001110000001 0 0 IN IP4 192.168.0.107
 
s=Play
 
c=IN IP4 192.168.0.107
 
t=0 0
 
m=video 19002 RTP/AVP 96
 
a=rtpmap:96 PS/90000
 
a=sendonly

第三步:【服务端>>客户端】回复ACK

ACK sip:34020000001310000002@4401020049 SIP/2.0
 
Call-ID: helloVideo
 
CSeq: 1 ACK
 
From: <sip:44010200492000000001@4401020049>;tag=bccedfd0111
 
To: <sip:34020000001110000001@4401020049>
 
Max-Forwards: 70
 
Via: SIP/2.0/UDP 192.168.0.107:5060;branch=z9hG4bKee5c5d98-00003
 
Content-Length: 0

第四步:播放码流

第五步:【服务端>>客户端】停止视频请求

BYE sip:34020000001310000002@4401020049 SIP/2.0
 
From: <sip:44010200492000000001@4401020049>;tag=bccedfd0111
 
To: sip:34020000001110000001@4401020049;tag=5f906952
 
CSeq: 2 BYE
 
Call-ID: helloVideo
 
Via: SIP/2.0/UDP 192.168.0.107:5060;branch=z9hG4bKee5c5d98-00004
 
Max-Forwards: 70
 
Content-Length: 0

第六步:【客户端】回应200

SIP/2.0 200 OK
 
Via: SIP/2.0/UDP 192.168.0.107:5060;branch=z9hG4bKee5c5d98-00004
 
From: <sip:44010200492000000001@4401020049>;tag=bccedfd0111
 
To: sip:34020000001110000001@4401020049;tag=5f906952
 
Call-ID: helloVideo
 
CSeq: 2 BYE
 
Server: Happytime Agent Ver 1.0
 
Content-Length: 0

第七步:【服务端】关闭视频端口

EasyGBS国标视频云服务平台视频能力丰富,部署灵活,既能作为业务平台使用,也能作为视频能力层被业务平台调用。平台目前已经在大量的项目中落地应用,如明厨亮灶、平安乡村、雪亮工程等。关于国标视频平台EasyGBS国标平台中的GB28181协议的解析就到此结束。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
在基于GBT28181的SIP协议组件开发中,SIP事件处理是非常重要的一环。本文将介绍如何使用eXosip2实现SIP事件处理流程。 eXosip2是一个基于SIP协议的开源软件,它提供了一个简单易用的SIP协议栈,并且支持SIP的基本功能,例如注册、呼叫、媒体处理等。在eXosip2中,SIP事件处理是通过回调函数实现的。 下面是eXosip2的SIP事件处理流程: 1. 初始化eXosip2 在使用eXosip2之前,需要先进行初始化操作。可以通过以下代码实现: ``` osip_mutex_init (); osip_cond_init (); osip_udp_init (0); osip_tcp_init (0); osip_tls_init (0); eXosip_init (); ``` 2. 注册SIP 在eXosip2中,注册SIP是通过eXosip_register_build()函数实现的。该函数会生成一个REGISTER请求,并且通过eXosip_register_send()函数发送该请求。在注册成功后,eXosip2会调用注册成功的回调函数。 以下是注册SIP的示例代码: ``` char *server_ip = "192.168.1.100"; char *username = "test"; char *password = "123456"; char *realm = "example.com"; osip_message_t *reg = NULL; int reg_id = -1; reg_id = eXosip_register_build_request (reg, "REGISTER", server_ip, NULL, NULL); osip_message_set_supported_header (reg, "path"); osip_message_set_supported_header (reg, "eventlist"); osip_message_set_supported_header (reg, "gruu"); osip_message_set_supported_header (reg, "sip.instance"); eXosip_lock (); eXosip_register_send_request (reg_id, reg); eXosip_unlock (); osip_message_free (reg); ``` 3. 处理SIP事件 在eXosip2中,SIP事件是通过回调函数处理的。例如,当收到INVITE请求时,eXosip2会调用INVITE请求的回调函数。以下是INVITE请求的回调函数示例代码: ``` int on_invite_recv (osip_event_t *evt) { osip_message_t *invite = NULL; int call_id = -1; char *remote_ip = NULL; int remote_port = -1; char *local_sdp = NULL; call_id = eXosip_event_get_id (evt); remote_ip = eXosip_event_get_remote_ip (evt); remote_port = eXosip_event_get_remote_port (evt); osip_message_init (&invite); osip_message_parse (invite, eXosip_event_get_request (evt)); local_sdp = "v=0\r\no=root 1 1 IN IP4 192.168.1.100\r\ns=session\r\nc=IN IP4 192.168.1.100\r\nt=0 0\r\nm=audio 7078 RTP/AVP 0 8 101\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-15\r\n"; eXosip_lock (); eXosip_call_build_answer (call_id, 200, &invite); eXosip_call_send_answer (call_id, local_sdp, strlen (local_sdp)); eXosip_unlock (); osip_message_free (invite); return OSIP_SUCCESS; } ``` 在上述示例代码中,当收到INVITE请求时,通过eXosip_event_get_request()函数获取INVITE请求,并通过osip_message_parse()函数解析该请求。然后,生成一个200 OK的响应,并通过eXosip_call_send_answer()函数发送该响应。 4. 清理eXosip2 在使用完eXosip2后,需要对它进行清理操作。可以通过以下代码实现: ``` eXosip_quit (); osip_tls_deinit (); osip_tcp_deinit (); osip_udp_deinit (); osip_cond_deinit (); osip_mutex_deinit (); ``` 总结 通过以上介绍,我们了解了如何使用eXosip2实现SIP事件处理流程。在实际开发中,可以根据需要对eXosip2进行定制,以满足特定的业务需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦睡了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值