SRS SDP解析流程

本文涉及的产品
云解析 DNS,旗舰版 1个月
应用型负载均衡 ALB,每月750个小时 15LCU
公网NAT网关,每月750个小时 15CU
简介: SRS SDP解析流程

监听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_SIMULATOR
    if ((err = http_api_mux->handle("/rtc/v1/nack/", new SrsGoApiRtcNACK(this))) != srs_success) {
        return srs_error_wrap(err, "handle nack");
    }
#endif

    return 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 URL
    srs_parse_rtmp_url(streamurl, ruc.req_->tcUrl, ruc.req_->stream);
    // 解析RTC URL
    srs_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 config
    SrsConfDirective* 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.
    // 解析sdp
    if ((err = ruc.remote_sdp_.parse(remote_sdp_str)) != srs_success) {
   
   
        return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str());
    }
    // 校验SDP
    if ((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 sdp
    SrsRtcConnection* session = NULL;
    if ((err = server_->create_session(&ruc, local_sdp, &session)) != srs_success) {
   
   
        return srs_error_wrap(err, "create session");
    }

    //将answer sdp写入到ostringstream
    ostringstream 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-line
            break;
        }
        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 sdp
    if ((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 info
    if (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 sdp
    if ((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

总结

主要函数如下图:

相关文章
|
1月前
|
JavaScript 前端开发 开发者
Vue执行流程及渲染解析
【10月更文挑战第2天】
107 58
|
1月前
|
JavaScript 前端开发 UED
Vue执行流程及渲染解析
【10月更文挑战第5天】
|
1月前
|
存储 搜索推荐 数据库
运用LangChain赋能企业规章制度制定:深入解析Retrieval-Augmented Generation(RAG)技术如何革新内部管理文件起草流程,实现高效合规与个性化定制的完美结合——实战指南与代码示例全面呈现
【10月更文挑战第3天】构建公司规章制度时,需融合业务实际与管理理论,制定合规且促发展的规则体系。尤其在数字化转型背景下,利用LangChain框架中的RAG技术,可提升规章制定效率与质量。通过Chroma向量数据库存储规章制度文本,并使用OpenAI Embeddings处理文本向量化,将现有文档转换后插入数据库。基于此,构建RAG生成器,根据输入问题检索信息并生成规章制度草案,加快更新速度并确保内容准确,灵活应对法律与业务变化,提高管理效率。此方法结合了先进的人工智能技术,展现了未来规章制度制定的新方向。
36 3
|
1月前
|
存储 缓存 边缘计算
揭秘直播带货背后的黑科技:播放流程全解析!
大家好,我是小米,今天聊聊社区直播带货的技术细节。我们将探讨直播播放流程中的关键技术,包括 HTTP DASH 协议、POP(Point of Presence)缓存和一致性哈希算法等。通过这些技术,直播流能根据网络状况动态调整清晰度,保证流畅体验。POP 和 DC 的多层次缓存设计减少了延迟,提升了观看效果。无论是技术人员还是直播运营者,都能从中受益。希望通过本文,你能更好地理解直播背后的技术原理。
47 3
|
1月前
|
程序员 C++
C++编程:While与For循环的流程控制全解析
总结而言,`while`循环和 `for`循环各有千秋,它们在C++编程中扮演着重要的角色。选择哪一种循环结构应根据具体的应用场景、循环逻辑的复杂性以及个人的编程风格偏好来决定。理解这些循环结构的内在机制和它们之间的差异,对于编写高效、易于维护的代码至关重要。
56 1
|
1月前
|
敏捷开发 数据可视化 测试技术
解析软件项目管理:以板栗看板为例,其如何有效影响并优化软件开发流程
软件项目管理是一个复杂而重要的过程,涵盖了软件产品的创建、维护和优化。其核心目标是确保软件项目能够顺利完成,同时满足预定的质量、时间和预算目标。本文将深入探讨软件项目管理的内涵及其对软件开发过程的影响,并介绍一些有效的管理工具。
|
2月前
|
监控 数据挖掘 BI
项目管理流程全解析及关键步骤介绍
项目管理流程是项目成功的基石,涵盖启动、规划、执行、监控和收尾等阶段。Zoho Projects 等软件可提高效率,支持结构化启动与规划、高效执行与协作及实时监控。这些流程和工具对项目的全局视角、团队协作和风险控制至关重要。项目管理软件适用于不同规模企业,实施时间因软件复杂度和企业准备而异。
86 2
|
1月前
|
JSON JavaScript API
商品详情数据接口解析返回的JSON数据(API接口整套流程)
商品详情数据接口解析返回的JSON数据是API接口使用中的一个重要环节,它涉及从发送请求到接收并处理响应的整个流程。以下是一个完整的API接口使用流程,包括如何解析返回的JSON数据:
|
3月前
|
测试技术 持续交付 UED
下一篇
无影云桌面