WebRTC的好处不用在此描述了,zlm作为流媒体服务器对其支持的已经非常好了。接下来主要研究web端拉流的情况。
在浏览器输入https://服务器IP/webrtc,获取静态操作页面。
源码流程
HttpSession::Handle_Req_GET_lHttpFileManager::onAccessPathHttpFileManager::onAccessPathauto 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。
在web页面上点击开始,会向服务器请求如下命令,同时携带着web端的SDP数据。
POST /index/api/webrtc?app=live&stream=test&type=play HTTP/1.1
服务器接到该命令后的反应:
HttpSession::onRecvHeaderHttpSession::Handle_Req_POSTHttpSession::Handle_Req_POSTif (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(*this));
HTTP事件安装
// 主函数中调用web接口安装函数
installWebApiaddHttpListener();api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){auto type = allArgs["type"];auto offer = allArgs.getArgs();WebRtcPluginManager::Instance().getAnswerSdp(*(static_cast(&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(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::getAnswerSdpauto 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(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::createWebRtcPlayer::Ptr ret(new WebRtcPlayer(poller, src, info, preferred_tcp));ret->onCreate();WebRtcTransport::WebRtcTransport_identifier = "zlm_" + to_string(++s_key); // 设置该transport实例对应的标识,便于管理WebRtcTransportImp::onCreateWebRtcTransport::onCreate();registerSelf();WebRtcTransport::onCreate_dtls_transport = std::make_shared(_poller, this);_ice_server = std::make_shared(this, _identifier, makeRandStr(24));WebRtcTransportImp::registerSelf_self = static_pointer_cast(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 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::OnDtlsTransportConnectedonStartWebRTC();WebRtcPlayer::onStartWebRTCWebRtcTransportImp::onStartWebRTC();_reader = _play_src->getRing()->attach(getPoller(), true);weak_ptr weak_self = static_pointer_cast(shared_from_this());weak_ptr 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这块还需要深入研究,目前这块不熟悉。