2024年Go最新SRS SDP解析流程_srs publish 状态码返回400(2),解密Golang开发常见误区

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

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字符串




![img](https://img-blog.csdnimg.cn/img_convert/586a055d9e9daaa671800a46c0156942.png)
![img](https://img-blog.csdnimg.cn/img_convert/e345e460e68e342f8e7a48de288a9f44.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

code(os)`将SDP编码成响应的sdp字符串




[外链图片转存中...(img-LfUhf24X-1715639778859)]
[外链图片转存中...(img-RG5Z26Sx-1715639778859)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值