监听HTTP

推拉流HTTP API监听启动
文件位置:src/main/srs_app_rtc_server.cpp
推流是SrsGoApiRtcPublish类来处理,其具体处理函数实在do_serve_http

srs_error_t SrsRtcServer::listen_api()
{srs_error_t err = srs_success;// TODO: FIXME: Fetch api from hybrid manager, not from SRS.SrsHttpServeMux* http_api_mux = _srs_hybrid->srs()->instance()->api_server();if ((err = http_api_mux->handle("/rtc/v1/play/", new SrsGoApiRtcPlay(this))) != srs_success) {return srs_error_wrap(err, "handle play");}if ((err = http_api_mux->handle("/rtc/v1/publish/", new SrsGoApiRtcPublish(this))) != srs_success) {return srs_error_wrap(err, "handle publish");}#ifdef SRS_SIMULATORif ((err = http_api_mux->handle("/rtc/v1/nack/", new SrsGoApiRtcNACK(this))) != srs_success) {return srs_error_wrap(err, "handle nack");}
#endifreturn err;
}

在do_serve_http中,首先解析http请求参数,其参数含义:

  • sdp:请求端的sdp
  • streamurl:推流地址
  • api:url
  • clientip:请求端的ip地址,如果为空会默认设置为http请求端的IP
  • tid:应该类似标志的事务ID

然后再srs_parse_rtmp_url解析rtmp的URL,srs_discovery_tc_url解析RTC的URL。

srs_error_t SrsGoApiRtcPublish::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res)
{srs_error_t err = srs_success;//解析请求头// For each RTC session, we use short-term HTTP connection.SrsHttpHeader* hdr = w->header();hdr->set("Connection", "Close");//解析请求体// Parse req, the request json object, from body.SrsJsonObject* req = NULL;SrsAutoFree(SrsJsonObject, req);if (true) {string req_json;if ((err = r->body_read_all(req_json)) != srs_success) {return srs_error_wrap(err, "read body");}SrsJsonAny* json = SrsJsonAny::loads(req_json);if (!json || !json->is_object()) {return srs_error_new(ERROR_RTC_API_BODY, "invalid body %s", req_json.c_str());}req = json->to_object();}//获取请求参数// Fetch params from req object.SrsJsonAny* prop = NULL;if ((prop = req->ensure_property_string("sdp")) == NULL) {return srs_error_wrap(err, "not sdp");}string remote_sdp_str = prop->to_str();if ((prop = req->ensure_property_string("streamurl")) == NULL) {return srs_error_wrap(err, "not streamurl");}string streamurl = prop->to_str();string clientip;if ((prop = req->ensure_property_string("clientip")) != NULL) {clientip = prop->to_str();}if (clientip.empty()){clientip = dynamic_cast<SrsHttpMessage*>(r)->connection()->remote_ip();// Overwrite by ip from proxy.string oip = srs_get_original_ip(r);if (!oip.empty()) {clientip = oip;}}string api;if ((prop = req->ensure_property_string("api")) != NULL) {api = prop->to_str();}string tid;if ((prop = req->ensure_property_string("tid")) != NULL) {tid = prop->to_str();}// The RTC user config object.SrsRtcUserConfig ruc;ruc.req_->ip = clientip;ruc.api_ = api;// 解析RTMP URLsrs_parse_rtmp_url(streamurl, ruc.req_->tcUrl, ruc.req_->stream);// 解析RTC URLsrs_discovery_tc_url(ruc.req_->tcUrl, ruc.req_->schema, ruc.req_->host, ruc.req_->vhost, ruc.req_->app, ruc.req_->stream, ruc.req_->port, ruc.req_->param);// discovery vhost, resolve the vhost from configSrsConfDirective* parsed_vhost = _srs_config->get_vhost(ruc.req_->vhost);if (parsed_vhost) {ruc.req_->vhost = parsed_vhost->arg0();}if ((err = http_hooks_on_publish(ruc.req_)) != srs_success) {return srs_error_wrap(err, "RTC: http_hooks_on_publish");}// For client to specifies the candidate(EIP) of server.string eip = r->query_get("eip");if (eip.empty()) {eip = r->query_get("candidate");}string codec = r->query_get("codec");srs_trace("RTC publish %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s",streamurl.c_str(), api.c_str(), tid.c_str(), clientip.c_str(), ruc.req_->app.c_str(), ruc.req_->stream.c_str(),remote_sdp_str.length(), eip.c_str(), codec.c_str());ruc.eip_ = eip;ruc.codec_ = codec;ruc.publish_ = true;ruc.dtls_ = ruc.srtp_ = true;// TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information.// 解析sdpif ((err = ruc.remote_sdp_.parse(remote_sdp_str)) != srs_success) {return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str());}// 校验SDPif ((err = check_remote_sdp(ruc.remote_sdp_)) != srs_success) {return srs_error_wrap(err, "remote sdp check failed");}SrsSdp local_sdp;// TODO: FIXME: move to create_session.// Config for SDP and session.local_sdp.session_config_.dtls_role = _srs_config->get_rtc_dtls_role(ruc.req_->vhost);local_sdp.session_config_.dtls_version = _srs_config->get_rtc_dtls_version(ruc.req_->vhost);// Whether enabled.bool server_enabled = _srs_config->get_rtc_server_enabled();bool rtc_enabled = _srs_config->get_rtc_enabled(ruc.req_->vhost);if (server_enabled && !rtc_enabled) {srs_warn("RTC disabled in vhost %s", ruc.req_->vhost.c_str());}if (!server_enabled || !rtc_enabled) {return srs_error_new(ERROR_RTC_DISABLED, "Disabled server=%d, rtc=%d, vhost=%s",server_enabled, rtc_enabled, ruc.req_->vhost.c_str());}// TODO: FIXME: When server enabled, but vhost disabled, should report error.// 创建RTC会话,并写入answer sdpSrsRtcConnection* session = NULL;if ((err = server_->create_session(&ruc, local_sdp, &session)) != srs_success) {return srs_error_wrap(err, "create session");}//将answer sdp写入到ostringstreamostringstream os;if ((err = local_sdp.encode(os)) != srs_success) {return srs_error_wrap(err, "encode sdp");}string local_sdp_str = os.str();// Filter the \r\n to \\r\\n for JSON.string local_sdp_escaped = srs_string_replace(local_sdp_str.c_str(), "\r\n", "\\r\\n");// 设置响应参数res->set("code", SrsJsonAny::integer(ERROR_SUCCESS));res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server_id().c_str()));// TODO: add candidates in response json?res->set("sdp", SrsJsonAny::str(local_sdp_str.c_str()));res->set("sessionid", SrsJsonAny::str(session->username().c_str()));srs_trace("RTC username=%s, offer=%dB, answer=%dB", session->username().c_str(),remote_sdp_str.length(), local_sdp_escaped.length());srs_trace("RTC remote offer: %s", srs_string_replace(remote_sdp_str.c_str(), "\r\n", "\\r\\n").c_str());srs_trace("RTC local answer: %s", local_sdp_escaped.c_str());return err;
}

解析sdp

推流解析SDP

然后 ruc.remote_sdp_.parse(remote_sdp_str)解析SDP,SrsSdp::parse首先对读取每一行然后对行调用parse_line进行解析

srs_error_t SrsSdp::parse(const std::string& sdp_str)
{srs_error_t err = srs_success;// All webrtc SrsSdp annotated example// @see: https://tools.ietf.org/html/draft-ietf-rtcweb-SrsSdp-11// Sdp example// session info// v=// o=// s=// t=// media description// m=// a=// ...// media description// m=// a=// ...std::istringstream is(sdp_str);std::string line;// 以行读取while (getline(is, line)) {srs_verbose("%s", line.c_str());if (line.size() < 2 || line[1] != '=') {return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid sdp line=%s", line.c_str());}if (!line.empty() && line[line.size()-1] == '\r') {line.erase(line.size()-1, 1);}// Strip the space of line, for pion WebRTC client.line = srs_string_trim_end(line, " ");// 解析行if ((err = parse_line(line)) != srs_success) {return srs_error_wrap(err, "parse sdp line failed");}}// The msid/tracker/mslabel is optional for SSRC, so we copy it when it's empty.for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {SrsMediaDesc& media_desc = *iter;for (size_t i = 0; i < media_desc.ssrc_infos_.size(); ++i) {SrsSSRCInfo& ssrc_info = media_desc.ssrc_infos_.at(i);if (ssrc_info.msid_.empty()) {ssrc_info.msid_  = media_desc.msid_;}if (ssrc_info.msid_tracker_.empty()) {ssrc_info.msid_tracker_ = media_desc.msid_tracker_;}if (ssrc_info.mslabel_.empty()) {ssrc_info.mslabel_ = media_desc.msid_;}if (ssrc_info.label_.empty()) {ssrc_info.label_ = media_desc.msid_tracker_;}}}return err;
}

parse_line

srs_error_t SrsSdp::parse_line(const std::string& line)
{srs_error_t err = srs_success;std::string content = line.substr(2);switch (line[0]) {case 'o': {return parse_origin(content);}case 'v': {return parse_version(content);}case 's': {return parse_session_name(content);}case 't': {return parse_timing(content);}case 'a': {if (in_media_session_) {return media_descs_.back().parse_line(line);}return parse_attribute(content);}case 'm': {return parse_media_description(content);}case 'c': {// TODO: process c-linebreak;}default: {srs_trace("ignore sdp line=%s", line.c_str());break;}}return err;
}

校验sdp

check_remote_sdp对SDP进行校验,主要校验是否支持bound媒体复用和媒体能力

srs_error_t SrsGoApiRtcPublish::check_remote_sdp(const SrsSdp& remote_sdp)
{srs_error_t err = srs_success;if (remote_sdp.group_policy_ != "BUNDLE") {return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only support BUNDLE, group policy=%s", remote_sdp.group_policy_.c_str());}if (remote_sdp.media_descs_.empty()) {return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no media descriptions");}for (std::vector<SrsMediaDesc>::const_iterator iter = remote_sdp.media_descs_.begin(); iter != remote_sdp.media_descs_.end(); ++iter) {if (iter->type_ != "audio" && iter->type_ != "video") {return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "unsupport media type=%s", iter->type_.c_str());}if (! iter->rtcp_mux_) {return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only suppor rtcp-mux");}if (iter->recvonly_) {return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "publish API only support sendrecv/sendonly");}}return err;
}

生成SDP

生成本地sdp

server_->create_session进行媒体协商并创建会话信息和生成SDP

srs_error_t SrsRtcServer::create_session(SrsRtcUserConfig* ruc, SrsSdp& local_sdp, SrsRtcConnection** psession)
{...SrsRtcSource* source = NULL;// publish创建SrsRtcSource,即创建流,play绑定SrsRtcSource,即绑定流if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) {return srs_error_wrap(err, "create source");}...// TODO: FIXME: add do_create_session to error process.SrsRtcConnection* session = new SrsRtcConnection(this, cid);// 创建RTC会话,设置answer sdpif ((err = do_create_session(ruc, local_sdp, session)) != srs_success) {srs_freep(session);return srs_error_wrap(err, "create session");}*psession = session;return err;
}

将sdp编码成字符串

local_sdp.encode(os)将SDP编码成响应的sdp字符串

srs_error_t SrsSdp::encode(std::ostringstream& os)
{srs_error_t err = srs_success;os << "v=" << version_ << kCRLF;os << "o=" << username_ << " " << session_id_ << " " << session_version_ << " " << nettype_ << " " << addrtype_ << " " << unicast_address_ << kCRLF;os << "s=" << session_name_ << kCRLF;os << "t=" << start_time_ << " " << end_time_ << kCRLF;// ice-lite is a minimal version of the ICE specification, intended for servers running on a public IP address.os << "a=ice-lite" << kCRLF;if (!groups_.empty()) {os << "a=group:" << group_policy_;for (std::vector<std::string>::iterator iter = groups_.begin(); iter != groups_.end(); ++iter) {os << " " << *iter;}os << kCRLF;}os << "a=msid-semantic: " << msid_semantic_;for (std::vector<std::string>::iterator iter = msids_.begin(); iter != msids_.end(); ++iter) {os << " " << *iter;}os << kCRLF;if ((err = session_info_.encode(os)) != srs_success) {return srs_error_wrap(err, "encode session info failed");}for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {if ((err = (*iter).encode(os)) != srs_success) {return srs_error_wrap(err, "encode media description failed");}}return err;
}srs_error_t SrsRtcServer::do_create_session(SrsRtcUserConfig* ruc, SrsSdp& local_sdp, SrsRtcConnection* session)
{srs_error_t err = srs_success;SrsRequest* req = ruc->req_;// first add publisher/player for negotiate sdp media infoif (ruc->publish_) {if ((err = session->add_publisher(ruc, local_sdp)) != srs_success) {return srs_error_wrap(err, "add publisher");}} else {if ((err = session->add_player(ruc, local_sdp)) != srs_success) {return srs_error_wrap(err, "add player");}}...session->set_remote_sdp(ruc->remote_sdp_);// We must setup the local SDP, then initialize the session object.session->set_local_sdp(local_sdp);session->set_state(WAITING_STUN);// 初始化会话信息if ((err = session->initialize(req, ruc->dtls_, ruc->srtp_, username)) != srs_success) {return srs_error_wrap(err, "init");}// We allows username is optional, but it never empty here._srs_rtc_manager->add_with_name(username, session);return err;
}

add_publisher

add_publisher主要进行媒体协商并且生成SDP然后创建。

srs_error_t SrsRtcConnection::add_publisher(SrsRtcUserConfig* ruc, SrsSdp& local_sdp)
{srs_error_t err = srs_success;SrsRequest* req = ruc->req_;SrsRtcSourceDescription* stream_desc = new SrsRtcSourceDescription();SrsAutoFree(SrsRtcSourceDescription, stream_desc);// TODO: FIXME: Change to api of stream desc.//媒体协商if ((err = negotiate_publish_capability(ruc, stream_desc)) != srs_success) {return srs_error_wrap(err, "publish negotiate");}//生成 answer sdpif ((err = generate_publish_local_sdp(req, local_sdp, stream_desc, ruc->remote_sdp_.is_unified())) != srs_success) {return srs_error_wrap(err, "generate local sdp");}...source->set_stream_created();// Apply the SDP to source.source->set_stream_desc(stream_desc);// TODO: FIXME: What happends when error?if ((err = create_publisher(req, stream_desc)) != srs_success) {return srs_error_wrap(err, "create publish");}return err;
}

add_player

总结

主要函数如下图:

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

SRS SDP解析流程相关推荐

  1. SRS流媒体服务器SDP解析流程

    监听HTTP 推拉流HTTP API监听启动 文件位置:src/main/srs_app_rtc_server.cpp 推流是SrsGoApiRtcPublish类来处理,其具体处理函数实在do_se ...

  2. HTML页面加载和解析流程详细介绍

    浏览器加载和渲染html的顺序.如何加快HTML页面加载速度.HTML页面加载和解析流程等等,在本文将为大家详细介绍下,感兴趣的朋友不要错过 浏览器加载和渲染html的顺序 1. IE下载的顺序是从上 ...

  3. linux优化网页加载过程,HTML页面加载和解析流程 介绍

    1.浏览器加载和渲染html的顺序 1.1.IE下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的. 1.2.在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(并不是说所有相 ...

  4. Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析

    转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...

  5. Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下)

       本文原创, 转载请注明出处:http://blog.csdn.net/qinjuning 上篇文章<<Android中measure过程.WRAP_CONTENT详解以及xml布局文 ...

  6. Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)

    在之前一篇博文中<< Android中View绘制流程以及invalidate()等相关方法分析>> ,简单的阐述 了 Android View 绘制流程的三个步骤,即: 1. ...

  7. html中加载解析,HTML页面加载和解析流程详细介绍

    序言: 我一直都认为"网页制作"这个词是一个不怎么高端的词,在我的印象中网页制作的词是没有生命力的一个制作,我喜欢用HTML 这样简单直接,这词凸显高端,有大气漂亮的UI.一套完美 ...

  8. spring mvc @RequestBody @ResponseBody 解析流程

    一.此接口测试用例 @RequestMapping(value = "/save_user",produces = "application/xml;charset=ut ...

  9. java xml解析_XML的理解以及SAX解析流程

    XML一般用来存储数据作为数据存储的一种格式 XML还可以用于作为软件的参数配置 这里讲下如何理解java中的相对路径 在java中如何获取当前的路径,当我们知道当前的路径了以后我们就可以使用绝对路径 ...

最新文章

  1. trigger() --工作中问题nav样式
  2. 【Python】创建和使用类
  3. PLSQL_海量数据处理系列3_索引
  4. Android SDCard Mount 流程分析(一)
  5. VMware-使用VMware在本地搭建多个CentOS虚机
  6. Hadoop MapReduce的模式、算法和用例
  7. Centos7 WARNING: ‘aclocal-1.15‘ is missing on your system.
  8. [Todo] 乐观悲观锁,自旋互斥锁等等
  9. Windows® Server 2003 SP1 Platform SDK Web Install
  10. pcb 布线电容 影响延时_浅谈PCB布局布线对TVS防护效果的影响
  11. Java图书管理系统
  12. 直播视频网站源码,静态时钟
  13. 编程制作动态壁纸的思路_Android应用源码动态壁纸开发必看例子源码
  14. 我的前端故事----疯狂倒计时(requestAnimationFrame)
  15. 【Basis】狄利克雷分布
  16. PS高阶操作之字体特效
  17. Python 制作朋友圈高逼格的九宫格图片
  18. L1正则化优化问题的一种求解方法
  19. 大数据分析处理关键技术有哪些?
  20. 3X技术点亮发布会 雄迈十年产业融合再出发

热门文章

  1. 1123581321递归算法java_经典算法设计方法
  2. docker镜像启动后端口号是多少_RSS、智能家居、个人博客、维基百科……Docker 入门指南...
  3. [SPRD] 版本修改集锦
  4. Nmos驱动电路分析
  5. 使用python+selenium谷歌浏览器驱动查排名
  6. 失望时想起了你是什么歌_你是空你是空色即是空的空空什么歌 《想起了你》哪里听...
  7. POI导出word单元格合并
  8. 【世界杯赛程表】v1.3 100614 我做的M8软件!
  9. Recent Advances in Open Set Recognition A survey全文翻译精校
  10. 如何实现一个漂亮的微信小程序朋友圈、表白墙、校内圈(小程序篇,附源码)