基于WebRTC搭建直播平台

基于WebRTC搭建直播平台

直播可以说是近年来最火的互联网项目,各大直播平台如雨后春笋般先后兴起,转眼间主播这一行业也成为最赚钱的代名词。那我们就来从0开始搭建一个直播平台吧。

WebRTC

WebRTC,名称源自网页实时通信(Web Real-Time Communication)。是一个支持网页浏览器进行实时语音对话或视频对话的技术,谷歌于2010年收购获得。2011年5月开放了工程的源代码,成为下一代视频通话的标准。

优点

WebRTC作为一个面向网页浏览器的实时语音视频技术,主要有以下几个优点:

  • 具有良好的通用性,几乎在任何平台都可以正常使用。
  • 其使用的Interactive Connectivity Establishment(ICE)能让各个设备之间自动匹配当前最好的通讯方式,这是很多别的技术都不具备的。
  • 具备全双工的能力,即双向通讯(P2P),不仅可作为单向直播使用还能完成电子视频会议的双向音视频对话。
  • 为Google旗下,具有良好的发展前景,最重要的:开源

开始使用

导包

这里没有直接使用官方的原生库进行编译,因为太麻烦了,网上已经有组织提供了编译好的版本可以供我们直接使用,对库的提供者不是很了解,不知道是属于什么性质的,但是几乎市面上所有的Android端都是采用该库所以应该没有什么问题。有兴趣的也可以自己去官网编译。

implementation 'io.pristine:libjingle:11139@aar'

官网网址如下:

https://webrtc.org/native-code/android/

快速开始

以下步骤都是经过分类和优化后整理出来的,加上注释,大多都能读懂,因此就不详细解释了。如有不清楚的地方可以留言问我。

  • 初始化,Peer连接工厂类:

    //初始化 关键
    PeerConnectionFactory.initializeAndroidGlobals(context, true, true, true)
    factory = PeerConnectionFactory()
  • 获得Media,简单理解就是需要传输的音视频流:

    //获取视频源
    val videoCapture = VideoCapturerAndroid.create(CameraEnumerationAndroid.getNameOfBackFacingDevice())
    val videoSource = factory.createVideoSource(videoCapture, MediaConstraints())
    //获取音频源
    val audioSource = factory.createAudioSource(MediaConstraints())
    //获取封装MediaTrack
    val videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource)
    val audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource)
    //封装媒体流
    localMs = factory.createLocalMediaStream("ARDAMS")
    localMs?.addTrack(videoTrack)
    localMs?.addTrack(audioTrack)
    //预览
    preview?.invoke(localMs!!)
  • 配置ICE,进行网络连接:

    //Ice NAT穿透
    val iceList = ArrayList<PeerConnection.IceServer>()
    iceList.add(PeerConnection.IceServer("stun:23.21.150.121"))
    iceList.add(PeerConnection.IceServer("stun:stun.l.google.com:19302"))
    //媒体限制
    val constraints = MediaConstraints()
    constraints.mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"))
    constraints.mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"))
    constraints.optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"))
    pc = factory.createPeerConnection(iceList, constraints, Observer())
    pc?.addStream(localMs)

    这里使用的是stun方式,ICE逻辑判断逻辑很复杂,主要分为两种方式:STUN、TURN。

    STUN是将本地地址转换成外部可访问的公网地址,说白了有点像内网穿透的意思。

    TURN则是需要自己搭建,由公共中介服务器分配地址。

    有兴趣可以看我接下来的另一篇介绍ICE的博文。

  • 创建Offer:

    pc?.createOffer(Observer(), MediaConstraints())

    WebRTC模型中Peer之间需要进行信令交换,而承载信令的载体就是Offer/Answer,这里因为是实现的直播推流端,因此创建的是Offer,如果是观看端则需要创建Answer。

  • 信令交换:

    private inner class Observer : SdpObserver, PeerConnection.Observer {
    //创建offer成功
      override fun onCreateSuccess(p0: SessionDescription?) {
        log("offer:${p0?.description}")
        pc?.setLocalDescription(Observer(), p0)
        val jsonObject = JSONObject()
        jsonObject.put("id", "presenter")
        jsonObject.put("sdpOffer", p0?.description)
        SocketUtils.sendMsg(jsonObject.toString())
        log("json:$jsonObject")
      }
    
    //创建offer失败
      override fun onCreateFailure(p0: String?) {
        log("error: create offer $p0")
      }
    
    //当网络可用即Ice穿透成功
      override fun onIceCandidate(p0: IceCandidate?) {
        val jsonObject = JSONObject()
        jsonObject.put("id", "onIceCandidate")
        val ice = JSONObject()
        ice.put("sdpMid", p0?.sdpMid)
        ice.put("sdpMLineIndex", p0?.sdpMLineIndex)
        ice.put("usernameFragment", p0?.sdp?.substringAfter("ufrag ")?.substring(0, 4))
        ice.put("candidate", p0?.sdp)
        jsonObject.put("candidate", ice)
        SocketUtils.sendMsg(jsonObject.toString())
        log("iceCandidate:$p0")
        log("json:$jsonObject")
      }
    }

    通过Socket获得:

      private fun setRemoteSdp(sdp: String) {
          val answer = SessionDescription(SessionDescription.Type.ANSWER, sdp)
          pc?.setRemoteDescription(Observer(), answer)
      }
    
      private fun setRemoteIce(ice: EventIceCandidate) {
          ice.candidate?.let {
              pc?.addIceCandidate(IceCandidate(
                      it.sdpMid,
                      it.sdpMLineIndex,
                      it.candidate
              ))
          }
      }
  • 显示画面

          gl_view.preserveEGLContextOnPause = true
          gl_view.keepScreenOn = true
          VideoRendererGui.setView(gl_view) {
              RtcClient(this).preview = { localMs ->
                  // local and remote render
                  val localRender = VideoRendererGui.createGui(
                          LOCAL_X_CONNECTED, LOCAL_Y_CONNECTED,
                          LOCAL_WIDTH_CONNECTED, LOCAL_HEIGHT_CONNECTED,
                          RendererCommon.ScalingType.SCALE_ASPECT_FILL,
                          false)
                  localMs.videoTracks[0].addRenderer(localRender)
              }
          }

主要流程

直播端

  • 初始化,Peer连接工厂类。
  • 获取Media,创建GL渲染器,进入显示就绪状态。
  • 配置ICE并监听,当Candidate收集完成发送给远程端。
  • 创建Offer,当Offer创建完成发送给远程端。
  • 接收远程端发送的ICE信息,并设置给PeerConnection对象。
  • 接收远程端返回的Answer,并设置给PeerConnection对象。
  • 建立连接,开始直播。

观看端

  • 初始化,Peer连接工厂类。
  • 获取Media,创建GL渲染器,进入显示就绪状态。
  • 配置ICE并监听,当Candidate收集完成发送给远程端。
  • 接收远程端发送的ICE信息,并设置给PeerConnection对象。
  • 接收远程端发送的Offer,并设置给PeerConnection对象。
  • 创建Answer,当Answer创建完成发送给远程端。
  • 建立连接,开始直播。

猜你喜欢

转载自blog.csdn.net/ccw0054/article/details/79761160