zlm源码研究 - webrtc播放

背景

WebRTC的好处不用在此描述了,zlm作为流媒体服务器对其支持的已经非常好了。接下来主要研究web端拉流的情况。

获取静态页面

在浏览器输入https://服务器IP/webrtc,获取静态操作页面。
在这里插入图片描述
源码流程

HttpSession::Handle_Req_GET_l
  HttpFileManager::onAccessPath


HttpFileManager::onAccessPath
  auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl();
  MediaInfo media_info(fullUrl);
  auto file_path = getFilePath(parser, media_info, sender);
  if (File::is_dir(file_path.data()))
    auto indexFile = searchIndexFile(file_path);
    file_path = pathCat(file_path, indexFile);
    parser.setUrl(pathCat(parser.Url(), indexFile));
    accessFile(sender, parser, media_info, file_path, cb);
      HttpSession::HttpResponseInvoker invoker;
      invoker.responseFile();

读取www/index.html文件返回给web。

获取SDP

在web页面上点击开始,会向服务器请求如下命令,同时携带着web端的SDP数据。

POST /index/api/webrtc?app=live&stream=test&type=play HTTP/1.1

服务器接到该命令后的反应:

HttpSession::onRecvHeader
  HttpSession::Handle_Req_POST
   

HttpSession::Handle_Req_POST
  if (totalContentLen > 0 && (size_t)totalContentLen < maxReqSize )
    _contentCallBack = [this,parserCopy](const char *data,size_t len) {
            //恢复http头
            _parser = parserCopy;
            //设置content
            _parser.setContent(string(data,len));
            //触发http事件,emitHttpEvent内部会选择是否关闭连接
            emitHttpEvent(true);
            //清空数据,节省内存
            _parser.Clear();
            //content已经接收完毕
            return false;
        };


HttpSession::onRecvContent(const char *data,size_t len)
  if (_contentCallBack)
    _contentCallBack(data,len);        


HttpSession::emitHttpEvent
  // 广播HTTP事件
  NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,
                                     consumed,static_cast<SockInfo &>(*this));

HTTP事件安装

// 主函数中调用web接口安装函数
installWebApi
  addHttpListener();
  api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){
    auto type = allArgs["type"];
    auto offer = allArgs.getArgs();
    WebRtcPluginManager::Instance().getAnswerSdp(*(static_cast<Session *>(&sender)), type,
                                                   WebRtcArgsImp(allArgs, sender.getIdentifier()),
                                                   [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable {
            headerOut["Content-Type"] = HttpFileManager::getContentType(".json");
            headerOut["Access-Control-Allow-Origin"] = "*";
            val["sdp"] = const_cast<WebRtcInterface &>(exchanger).getAnswerSdp(offer);
            val["id"] = exchanger.getIdentifier();
            val["type"] = "answer";
            invoker(200, headerOut, val.toStyledString());
        });
    });


addHttpListener
   //注册监听kBroadcastHttpRequest事件
    NoticeCenter::Instance().addListener(&web_api_tag, Broadcast::kBroadcastHttpRequest,
                                         [](BroadcastHttpRequestArgs) {
      auto it = s_map_api.find(parser.Url());
      it->second(parser, invoker, sender);
	}                  

根据url找到对应的事件回调,最终会调用WebRtcPluginManager::Instance().getAnswerSdp。

WebRtcPluginManager::getAnswerSdp
  auto it = _map_creator.find(type);
  it->second(sender, args, cb);
  

// 静态注册插件
WebRtcPluginManager::Instance().registerPlugin("play", play_plugin);


void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginManager::onCreateRtc &cb)
  // 使用rtsp媒体源,两者均是传输的rtp流
  info._schema = RTSP_SCHEMA;
  MediaSource::findAsync(info, session_ptr, [=](const MediaSource::Ptr &src_in) mutable {
    auto src = dynamic_pointer_cast<RtspMediaSource>(src_in);
    // 还原成rtc,目的是为了hook时识别哪种播放协议
    info._schema = RTC_SCHEMA;
    auto rtc = WebRtcPlayer::create(EventPollerPool::Instance().getPoller(), src, info, preferred_tcp);
      cb(*rtc); // 发送answer SDP给web端
    });    

WebRtcPlayer创建

WebRtcPlayer::Ptr WebRtcPlayer::create
  WebRtcPlayer::Ptr ret(new WebRtcPlayer(poller, src, info, preferred_tcp));
  ret->onCreate();

  
WebRtcTransport::WebRtcTransport
  _identifier = "zlm_" + to_string(++s_key); // 设置该transport实例对应的标识,便于管理


WebRtcTransportImp::onCreate
  WebRtcTransport::onCreate();
  registerSelf();
  

WebRtcTransport::onCreate
  _dtls_transport = std::make_shared<RTC::DtlsTransport>(_poller, this);
  _ice_server = std::make_shared<RTC::IceServer>(this, _identifier, makeRandStr(24));


WebRtcTransportImp::registerSelf
  _self = static_pointer_cast<WebRtcTransportImp>(shared_from_this());
  WebRtcTransportManager::Instance().addItem(getIdentifier(), _self);

拉流

在这里插入图片描述

Web端首先根据协商的IP和端口,服务端webrtc的端口是8000,发送STUN命令再次获取STUN地址。
首次连接,服务端会创建对应的session。

WebRtcSession::WebRtcSession(const Socket::Ptr &sock) : Session(sock)
  socklen_t addr_len = sizeof(_peer_addr);
  getpeername(sock->rawFD(), (struct sockaddr *)&_peer_addr, &addr_len);
  

WebRtcSession::onRecv_l(const char *data, size_t len)
  // 首次进入,根据username获取之前创建的transport.
  auto user_name = getUserName(data, len); // 此处的username就是之前设置的transport标识
  auto transport = WebRtcTransportManager::Instance().getItem(user_name);
  transport->setSession(shared_from_this());
  _transport = std::move(transport);
  _transport->inputSockData((char *)data, len, (struct sockaddr *)&_peer_addr);
  
  
WebRtcTransport::inputSockData
  // 处理STUN消息
  if (RTC::StunPacket::IsStun((const uint8_t *)buf, len))
    std::unique_ptr<RTC::StunPacket> packet(RTC::StunPacket::Parse((const uint8_t *)buf, len));
    _ice_server->ProcessStunPacket(packet.get(), tuple);
    return;
    
  // 处理    
  if (is_dtls(buf))
    _dtls_transport->ProcessDtlsData((uint8_t *)buf, len);
    return;
    
  // 由于是拉流,不存在rtp数据,但是有rtcp数据
  if (is_rtcp(buf))
    if (_srtp_session_recv->DecryptSrtcp((uint8_t *)buf, &len))
      onRtcp(buf, len);

DTLS交互完成后,接下来启动媒体传输

WebRtcTransport::OnDtlsTransportConnected
  onStartWebRTC();


WebRtcPlayer::onStartWebRTC
  WebRtcTransportImp::onStartWebRTC();
  _reader = _play_src->getRing()->attach(getPoller(), true);
  weak_ptr<WebRtcPlayer> weak_self = static_pointer_cast<WebRtcPlayer>(shared_from_this());
  weak_ptr<Session> weak_session = getSession();
  _reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) {
    size_t i = 0;
    pkt->for_each([&](const RtpPacket::Ptr &rtp) {
      strong_self->onSendRtp(rtp, ++i == pkt->size());
    });
  });

媒体的Qos这块还需要深入研究,目前这块不熟悉。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值