最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • WebRTC的初探之路

    正文概述 掘金(合度)   2021-03-18   896

    WebRTC的初探之路

    MDN

    介绍什么是WebRTC

    WebRTC ( Web Real-Time Communications 网页即时通信) 是一项网页实时通讯技术,(以目前所熟知的大多数直播软件,或是远程会议,视频通话类软件,都是借助于特定的客户端做视频数据的推流工作,客户端进行拉流播放)WebRTC建立浏览器之间点对点(Peer-to-Peer,P2P) 连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。并且于 2011年6月1日开源,并在Google、Mozilla、Opera的支持下被纳入万维网联盟的W3C推荐标准,它通过简单的API为浏览器和移动应用程序提供实时通信(RTC)的功能。

    WebRTC目前主要的应用领域如下。

    WebRTC项目的原则是API开源、免费、标准化、浏览器内置,比现有的技术更高效。WebRTC虽然冠以“Web”之名,但并不受限于传统互联网应用或浏览器的终端运行环境。实际上,无论终端运行环境是浏览器、桌面应用、移动设备(Android或iOS)还是IoT设备,只要IP连接可到达且符合WebRTC规范就可以互通。这一点释放了大量智能终端(或运行在智能终端上的App)的实时通信能力,打开了许多对于实时交互性要求较高的应用场景的想象空间,音视频会议·在线教育·照相机·音乐播放器·共享远程桌面·录制·即时通信工具·P2P网络加速·文件传输工具·游戏·实时人脸识别,都是其合适的应用领域,

    WebRTC整体架构

    WebRTC的初探之路Web应用:Web开发者可以基于Web API开发基于视频、音频的实时通信应用,如视频会议、远程教育、视频通话、视频直播、游戏直播、远程协作、互动游戏、实时人脸识别等

    1. Web API:Web API是面向第三方开发者的WebRTC标准API(JavaScript)常用的API如下所示
    MediaStream:媒体数据流,如音频流、视频流等。
    RTCPeerConnection:该类很重要,提供了应用层的调用接口。
    RTCDataChannel:传输非音视频数据,如文字、图片等
    
    1. C++ API 层 有C++语言编写,使浏览器厂商容易实现WebRTC标准的Web API,抽象地对数字信号过程进行处理。如 RTCPeerConnection API是每个浏览器之间点对点连接的核心,RTCPeerConnection是WebRTC组件,用于处理点对点间流数据的稳定和有效通信。

    2. Session Management 是一个抽象的会话层,提供会话建立和管理功能。该层协议留给应用开发者自定义实现。对于Web应用,建议使用WebSocket技术来管理信令Session, 信令主要用来转发会话双方的媒体信息和网络信息。也是后端开发者需要关注的一层

    3. Transport 为WebRTC的传输层,涉及音视频的数据发送、接收、网络打洞等内容,可以通过STUN和ICE组件来建立不同类型的网络间的呼叫连接 关注与 P2P 这里

    4. VideoEngine是WebRTC视频处理引擎, 包含一系列视频处理的整体框架,从摄像头采集视频到视频信息网络传输再到视频显示,是一个完整过程的解决方案

      • VP8是视频图像编解码器,也是WebRTC视频引擎默认的编解码器。VP8适合实时通信应用场景,因为它主要是针对低延时而设计的编解码器。VPx编解码器是Google收购ON2公司后开源的,现在是WebM项目的一部分,而WebM项目是Google致力于推动的HTML5标准之一。
      • Video Jitter Buffer(视频抖动缓冲器模块可以降低由于视频抖动和视频信息包丢失带来的不良影响。
      • Image Enhancements(图像质量增强模块对网络摄像头采集到的视频图像进行处理,包括明暗度检测、颜色增强、降噪处理等功能,用来提升视频质量
    5. VoiceEngine(音频引擎)是包含一系列音频多媒体处理的框架,包括从音频采集到网络传输端等整个解决方案。VoiceEngine是WebRTC极具价值的技术之一,是Google收购GIPS公司后开源的,目前在VoIP技术上处于业界领先地位

      • iSAC 是针对VoIP和音频流的宽带和超宽带音频编解码器 是WebRTC音频引擎的默认编解码器
      • iLBC是VoIP音频流的窄带语音编解码器
      • NetEQ For Voice是针对音频软件实现的语音信号处理元件 能够有效地处理网络抖动和语音包丢失时对语音质量产生的影响
      • Acoustic Echo Canceler(AEC,回声消除器)是一个基于软件的信号处理元件,能实时地去除麦克风采集到的回声。
      • Noise Reduction(NR,噪声抑制)也是一个基于软件的信号处理元件,用于消除与相关VoIP的某些类型的背景噪声 如嘶嘶声、风扇噪音等

    WebRTC通话原理

    MDNWebRTC

    通话的大致可以分为三个步骤 (假定通话的双方为Alice和Bob。双方要建立起通话,主要步骤如下所示)

    one: 媒体协商Alice 与 Bob 通过信令服务器进行媒体协商,如双方使用的音视频编码格式。双方交换的媒体数据由SDP(Session Description Protocol,会话描述协议)描述

    two: 网络协商Alice 与 Bob 通过STUN服务器获取到各自的网络信息,如IP和端口。然后通过信令服务器转发,互相交换各种网络信息。这样双方就知道对方的IP和端口了,即P2P打洞成功建立直连。这个过程涉及NAT及ICE协议。

    three: 建立连接Alice 与 Bob 如果没有建立起直连,则通过TURN中转服务器转发音视频数据,最终完成音视频通话

    1、媒体协商

    媒体协商就是双方在建立连接之前 必须告诉对方 用什么样的媒体格式 ,了解对方支持的媒体格式 才能保证后续正确的编解码 Alice 端可支持VP8、H264多种编码格式,而 Bob 端支持VP9、H264 通过媒体协商 取他们的交集H264来编解码视频。

    描述媒体连接内容的协议叫(Session Description Protocol) 简称 (SDP) 内容 例如分辨率,格式,编码,加密算法等。所以在数据传输时两端都能够理解彼此的数据。SDP并不是一个真正的协议,而是一种数据格式,用于描述在设备之间共享媒体的连接的元数据。 那SDP 信息从哪里来呢?一般来说,在建立连接之前,连接双方需要先通过 RTCPeerConnection API来指定自己要传输什么数据(如Audio、Video、DataChannel) 然后通过 CreateOffer() CreateAnswer() 方法创建 SDP 信息

    SDP 信息的交换 要借助于 信令服务器 可以用来交换双方的SDP信息,一般是通过创建Socket连接进行交互处理。可以使用Node.js、Golang或其他技术,只要能交换双方的SDP数据即可。

    贴一个SDP数据 -- 就下面这玩意

    v=0
    o=- 7524998691693353763 2 IN IP4 127.0.0.1
    s=-
    t=0 0
    a=group:BUNDLE 0 1
    a=msid-semantic: WMS kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ
    m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
    c=IN IP4 0.0.0.0
    a=rtcp:9 IN IP4 0.0.0.0
    a=ice-ufrag:hwkT
    a=ice-pwd:tXV1yDOgQpS9bBHqY5w+/oGf
    a=ice-options:trickle
    a=fingerprint:sha-256 54:9D:F1:8C:46:89:61:24:FC:B1:5C:F6:6E:BF:18:AF:22:CD:A0:37:37:64:37:61:D6:FF:4F:0D:C2:70:7B:A4
    a=setup:actpass
    a=mid:0
    a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
    a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
    a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
    a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
    a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
    a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
    a=sendrecv
    a=msid:kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ 6426930d-bb60-4633-b8b5-bb91d19d8430
    a=rtcp-mux
    a=rtpmap:111 opus/48000/2
    a=rtcp-fb:111 transport-cc
    a=fmtp:111 minptime=10;useinbandfec=1
    a=rtpmap:103 ISAC/16000
    a=rtpmap:104 ISAC/32000
    a=rtpmap:9 G722/8000
    a=rtpmap:0 PCMU/8000
    a=rtpmap:8 PCMA/8000
    a=rtpmap:106 CN/32000
    a=rtpmap:105 CN/16000
    a=rtpmap:13 CN/8000
    a=rtpmap:110 telephone-event/48000
    a=rtpmap:112 telephone-event/32000
    a=rtpmap:113 telephone-event/16000
    a=rtpmap:126 telephone-event/8000
    a=ssrc:5150036 cname:+RCf3A8Ya1BflCDM
    a=ssrc:5150036 msid:kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ 6426930d-bb60-4633-b8b5-bb91d19d8430
    a=ssrc:5150036 mslabel:kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ
    a=ssrc:5150036 label:6426930d-bb60-4633-b8b5-bb91d19d8430
    m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 124 119 123 118 114 115 116
    c=IN IP4 0.0.0.0
    a=rtcp:9 IN IP4 0.0.0.0
    a=ice-ufrag:hwkT
    a=ice-pwd:tXV1yDOgQpS9bBHqY5w+/oGf
    a=ice-options:trickle
    a=fingerprint:sha-256 54:9D:F1:8C:46:89:61:24:FC:B1:5C:F6:6E:BF:18:AF:22:CD:A0:37:37:64:37:61:D6:FF:4F:0D:C2:70:7B:A4
    a=setup:actpass
    a=mid:1
    a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
    a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
    a=extmap:13 urn:3gpp:video-orientation
    a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
    a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
    a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
    a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
    a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
    a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
    a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
    a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
    a=sendrecv
    a=msid:kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ 1cfb3b88-4ed0-4267-9c1e-c861e2a323cb
    a=rtcp-mux
    a=rtcp-rsize
    a=rtpmap:96 VP8/90000
    a=rtcp-fb:96 goog-remb
    a=rtcp-fb:96 transport-cc
    a=rtcp-fb:96 ccm fir
    a=rtcp-fb:96 nack
    a=rtcp-fb:96 nack pli
    a=rtpmap:97 rtx/90000
    a=fmtp:97 apt=96
    a=rtpmap:98 VP9/90000
    a=rtcp-fb:98 goog-remb
    a=rtcp-fb:98 transport-cc
    a=rtcp-fb:98 ccm fir
    a=rtcp-fb:98 nack
    a=rtcp-fb:98 nack pli
    a=fmtp:98 profile-id=0
    a=rtpmap:99 rtx/90000
    a=fmtp:99 apt=98
    a=rtpmap:100 VP9/90000
    a=rtcp-fb:100 goog-remb
    a=rtcp-fb:100 transport-cc
    a=rtcp-fb:100 ccm fir
    a=rtcp-fb:100 nack
    a=rtcp-fb:100 nack pli
    a=fmtp:100 profile-id=2
    a=rtpmap:101 rtx/90000
    a=fmtp:101 apt=100
    a=rtpmap:102 H264/90000
    a=rtcp-fb:102 goog-remb
    a=rtcp-fb:102 transport-cc
    a=rtcp-fb:102 ccm fir
    a=rtcp-fb:102 nack
    a=rtcp-fb:102 nack pli
    a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
    a=rtpmap:121 rtx/90000
    a=fmtp:121 apt=102
    a=rtpmap:127 H264/90000
    a=rtcp-fb:127 goog-remb
    a=rtcp-fb:127 transport-cc
    a=rtcp-fb:127 ccm fir
    a=rtcp-fb:127 nack
    a=rtcp-fb:127 nack pli
    a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
    a=rtpmap:120 rtx/90000
    a=fmtp:120 apt=127
    a=rtpmap:125 H264/90000
    a=rtcp-fb:125 goog-remb
    a=rtcp-fb:125 transport-cc
    a=rtcp-fb:125 ccm fir
    a=rtcp-fb:125 nack
    a=rtcp-fb:125 nack pli
    a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
    a=rtpmap:107 rtx/90000
    a=fmtp:107 apt=125
    a=rtpmap:108 H264/90000
    a=rtcp-fb:108 goog-remb
    a=rtcp-fb:108 transport-cc
    a=rtcp-fb:108 ccm fir
    a=rtcp-fb:108 nack
    a=rtcp-fb:108 nack pli
    a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
    a=rtpmap:109 rtx/90000
    a=fmtp:109 apt=108
    a=rtpmap:124 H264/90000
    a=rtcp-fb:124 goog-remb
    a=rtcp-fb:124 transport-cc
    a=rtcp-fb:124 ccm fir
    a=rtcp-fb:124 nack
    a=rtcp-fb:124 nack pli
    a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
    a=rtpmap:119 rtx/90000
    a=fmtp:119 apt=124
    a=rtpmap:123 H264/90000
    a=rtcp-fb:123 goog-remb
    a=rtcp-fb:123 transport-cc
    a=rtcp-fb:123 ccm fir
    a=rtcp-fb:123 nack
    a=rtcp-fb:123 nack pli
    a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
    a=rtpmap:118 rtx/90000
    a=fmtp:118 apt=123
    a=rtpmap:114 red/90000
    a=rtpmap:115 rtx/90000
    a=fmtp:115 apt=114
    a=rtpmap:116 ulpfec/90000
    a=ssrc-group:FID 3517908871 1250619161
    a=ssrc:3517908871 cname:+RCf3A8Ya1BflCDM
    a=ssrc:3517908871 msid:kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ 1cfb3b88-4ed0-4267-9c1e-c861e2a323cb
    a=ssrc:3517908871 mslabel:kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ
    a=ssrc:3517908871 label:1cfb3b88-4ed0-4267-9c1e-c861e2a323cb
    a=ssrc:1250619161 cname:+RCf3A8Ya1BflCDM
    a=ssrc:1250619161 msid:kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ 1cfb3b88-4ed0-4267-9c1e-c861e2a323cb
    a=ssrc:1250619161 mslabel:kFj1r3NdWzKanYt530Rbg0QQk8DbMwv2eXuJ
    a=ssrc:1250619161 label:1cfb3b88-4ed0-4267-9c1e-c861e2a323cb
    

    2、网络协商

    媒体协商需要通信双方彼此要了解对方的网络情况 。这样才有可能找到一条通信链路。需要做以下两个处理

    • 1、获取外网IP地址映射
    • 2、通过信令服务器交换 "网络信息"

    理想的网络情况是每个浏览器所在的计算机IP都是公网IP,可以直接进行点对点连接 ,但是 理想很丰满 现实很骨感 实际情况是我们的计算机都是在某个局域网中并且有防火墙,需要进行网络地址转换(Network Address Translation,NAT)在解决WebRTC使用过程中的上述问题时,我们需要用到NAT、STUN和TURN等概念,下面分别介绍。

    NAT

    NAT 简单来说,NAT是为了解决IPv4下的IP地址匮乏而出现的一种技术。例如,通常我们处在一个路由器之下 路由器的WAN口有一个公网IP,而所有连接路由器LAN口的设备会分配一个私有的的地址通常为 192.168.1.1、192.168.1.2,如果有n个设备,可能分配到192.168.1.n,而这个IP地址显然只是一个内网的IP地址,这样一个路由器的公网地址对应了n个内网的地址,这种使用少量的公有IP地址代表较多的私有IP地址的方式,将有助于减缓IP地址空间的枯竭, NAT技术会保护内网地址的安全性,所以这就会引发一个问题,就是当我们采用P2P中的连接方式时,NAT会阻止外网地址的访问,这时我们就得采用NAT穿透技术了

    可以借助一个公网IP服务器,Alice与Bob都往公网IP-PORT 发包,公网服务器就可以获知 Alice 与 Bob 的IP/PORT,又由于Alice与Bob主动给公网IP服务器发包,所以公网服务器可以穿透NAT- Alice 与 NAT-Bob,并发送包给Alice与Bob。所以只要公网IP将Bob的IP/PORT 发给Alice,将Alice的IP/PORT发给Bob,这样下次Alice与Bob互相发送消息时,就不会被NAT阻拦了 , WebRTC的防火墙穿透技术就是基于上述思路来实现的。在WebRTC中采用ICE框架来保证RTCPeerConnection能实现NAT穿透。

    ICE

    交互式连接设施Interactive Connectivity Establishment (ICE) 是一个允许你的浏览器和对端浏览器建立连接的协议框架,ICE通过使用以下几种技术完成上述工作

    STUN (Session Traversal Utilities for NAT)

    STUN是一种网络协议,即简单的用UDP穿透NAT, 它允许位于 NAT(或多重NAT)后的客户端 [处于局域网中的计算机] 找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端口。这些信息被用来在两个同时处于NAT路由器之后的主机之间创建UDP通信 但是 But 通过STUN服务器取得了公网IP位址,也不一定能建立连接。 这是 因为不同的NAT类型处理传入的UDP分组的方式是不同的,四种主要类型中有三种可以使用STUN穿透:完全圆锥型NAT受限圆锥型NAT端口受限圆锥型NAT。但大型公司网络中经常采用的对称型NAT(又称为双向NAT)则不能使用,这类路由器会透过NAT部署所谓的“Symmetric NAT限制”,也就是说,路由器只会接受你之前连线过的节点所建立的连线,这类网络就需要用到TURN技术

    • Full Cone NAT (完全锥形NAT)

    完全锥形NAT,所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP和端口号,并且任何一个外网主机都可以通过这个映射的外网IP和端口号向这台内网主机发送包。

    • Restricted Cone NAT(限制锥形NAT)

    限制锥形NAT,它也是所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP和端口号。与完全锥形不同的是,外网主机只能够向先前已经向它发送过数据包的内网主机发送包。

    • Port Restricted Cone NAT (端口限制锥形NAT)

    端口限制锥形NAT,与限制锥形NAT很相似,只不过它包括端口号。也就是说,一台IP地址X和端口P的外网主机想给内网主机发送包,必须是这台内网主机先前已经给这个IP地址X和端口P发送过数据包。

    • Symmetric NAT

    对称NAT,所有从同一个内网IP和端口号发送到一个特定的目的IP和端口号的请求,都会被映射到同一个IP和端口号。如果同一台主机使用相同的源地址和端口号发送包,但是发往不同的目的地,NAT将会使用不同的映射。此外,只有收到数据的外网主机才可以反过来向内网主机发送包

    TURN (Traversal Using Relays around NAT (TURN) 

    TURN是指使用中继穿透NAT ,主要添加了中继功能。如果终端在进行NAT之后,在特定的情景下有可能使得终端无法和其他终端进行直接的通信,这时就需要将公网的服务器作为一个中继,对来往的数据进行转发。这个转发采用的协议就是TURN

    STUN服务器和TURN服务器我们使用coturn开源项目来搭建,地址为github.com/coturn/cotu…。也可以使用以Golang技术开发的服务器来搭建,地址为github.com/pion/turn

    信令服务器

    信令服务器不只是交换SDP和Candidate,还有其他功能,比如房间管理、用户列表、用户进入、用户退出等IM功能

    3、连接建立

    1)连接双方(Peer)通过第三方服务器来交换(signaling)各自的SDP数据。
    2)连接双方通过STUN协议从STUN服务器那里获取到自己的NAT结构、子网IP和公网IP、端口,即Candidate信息。
    3)连接双方通过第三方服务器来交换各自的Candidate,如果连接双方在同一个NAT下,那它们仅通过内网 Candidate就能建立起连接;如果它们处于不同NAT下,就需要通过STUN服务器识别出的公网Candidate进行通信。
    4)如果仅通过STUN服务器发现的公网Candidate仍然无法建立连接,这就需要寻求TURN服务器提供的转发服务,然后将转发形式的Candidate共享给对方。
    5)连接双方向目标IP端口发送报文,通过SDP数据中涉及的密钥以及期望传输的内容建立起加密长连接。

    WebRTC的初探之路

    标注的场景是Alice向Bob发起对聊请求
    1、Alice 首先创建PeerConnection对象,然后打开本地音视频设备,将音视频数据封装成MediaStream添加到PeerConnection中
    2、Alice 调用PeerConnectionCreateOffer方法创建一个用于offer的SDP对象,SDP对象中保存当前音视频的相关参数。
    3、Alice 通过PeerConnectionSetLocalDescription方法将该SDP对象保存起来,并通过信令服务器发送给 Bob
    4、Bob接收到Alice发送过的offer SDP 对象,通过PeerConnectionSetRemoteDescription方法将其保存起来、
    5、并且Bob调用PeerConnectionCreateAnswer方法创建一个应答的SDP对象,通过PeerConnectionSetLocalDescription的方法保存该应答SDP对象
    6、并且Bob需要将创建的应答的SDP对象通过信令服务器发送给Alice
    7、Alice 接收到 Bob 发送过来的应答SDP对象,将其通过PeerConnectionSetRemoteDescription方法保存起来
    8、在SDP信息的 offer/answer流程中,Alice和Bob已经根据SDP信息创建好相应的音频l和视频,并且通过NAT穿透获取了Candidate数据,Candidate数据 包含了彼此的IP地址信息(本地IP地址、公网IP地址)和端口信息
    9、当Alice收集到Candidate信息后,PeerConnection会通过OnIceCandidate接口给Alice发送通知,Alice将收到的Candidate信息通过信令服务器发送给Bob,Bob通过PeerConnectionAddIceCandidate方法保存起来
    10、同样的操作Blice对Alice再来一次。
    11、这样Alice和Bob就已经建立了音视频传输的P2P通道,Bob接收到Alice传送过来的音视频流,会通过PeerConnectionOnAddStream回调接口返回一个标识Alice端音视频流的MediaStream对象,在Bob端渲染出来即可 -- A、B收到对方的媒体流并播放


    起源地下载网 » WebRTC的初探之路

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元