No flags found
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
e0c8c19
... +5 ...
686f577
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
121 | 121 | ||
122 | 122 | srs_parse_rtmp_url(streamurl, ruc.req_->tcUrl, ruc.req_->stream); |
|
123 | 123 | ||
124 | - | srs_discovery_tc_url(ruc.req_->tcUrl, ruc.req_->schema, ruc.req_->host, ruc.req_->vhost, |
|
124 | + | srs_discovery_tc_url(ruc.req_->tcUrl, ruc.req_->schema, ruc.req_->host, ruc.req_->vhost, |
|
125 | 125 | ruc.req_->app, ruc.req_->stream, ruc.req_->port, ruc.req_->param); |
|
126 | 126 | ||
127 | 127 | // discovery vhost, resolve the vhost from config |
130 | 130 | ruc.req_->vhost = parsed_vhost->arg0(); |
|
131 | 131 | } |
|
132 | 132 | ||
133 | - | if ((err = http_hooks_on_play(ruc.req_)) != srs_success) { |
|
134 | - | return srs_error_wrap(err, "RTC: http_hooks_on_play"); |
|
135 | - | } |
|
136 | - | ||
137 | 133 | // For client to specifies the candidate(EIP) of server. |
|
138 | 134 | string eip = r->query_get("eip"); |
|
139 | 135 | if (eip.empty()) { |
144 | 140 | string srtp = r->query_get("encrypt"); |
|
145 | 141 | string dtls = r->query_get("dtls"); |
|
146 | 142 | ||
147 | - | srs_trace("RTC play %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s, srtp=%s, dtls=%s", |
|
148 | - | 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(), |
|
149 | - | eip.c_str(), codec.c_str(), srtp.c_str(), dtls.c_str() |
|
143 | + | srs_trace( |
|
144 | + | "RTC play %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s, srtp=%s, dtls=%s", |
|
145 | + | streamurl.c_str(), api.c_str(), tid.c_str(), clientip.c_str(), ruc.req_->app.c_str(), |
|
146 | + | ruc.req_->stream.c_str(), remote_sdp_str.length(), |
|
147 | + | eip.c_str(), codec.c_str(), srtp.c_str(), dtls.c_str() |
|
150 | 148 | ); |
|
151 | 149 | ||
152 | 150 | ruc.eip_ = eip; |
161 | 159 | } |
|
162 | 160 | ||
163 | 161 | // TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information. |
|
162 | + | ruc.remote_sdp_str_ = remote_sdp_str; |
|
164 | 163 | if ((err = ruc.remote_sdp_.parse(remote_sdp_str)) != srs_success) { |
|
165 | 164 | return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str()); |
|
166 | 165 | } |
169 | 168 | return srs_error_wrap(err, "remote sdp check failed"); |
|
170 | 169 | } |
|
171 | 170 | ||
171 | + | if ((err = http_hooks_on_play(ruc.req_)) != srs_success) { |
|
172 | + | return srs_error_wrap(err, "RTC: http_hooks_on_play"); |
|
173 | + | } |
|
174 | + | ||
175 | + | if ((err = serve_http(w, r, &ruc)) != srs_success) { |
|
176 | + | return srs_error_wrap(err, "serve"); |
|
177 | + | } |
|
178 | + | ||
179 | + | res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); |
|
180 | + | res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server_id().c_str())); |
|
181 | + | ||
182 | + | // TODO: add candidates in response json? |
|
183 | + | res->set("sdp", SrsJsonAny::str(ruc.local_sdp_str_.c_str())); |
|
184 | + | res->set("sessionid", SrsJsonAny::str(ruc.session_id_.c_str())); |
|
185 | + | ||
186 | + | return err; |
|
187 | + | } |
|
188 | + | ||
189 | + | srs_error_t SrsGoApiRtcPlay::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsRtcUserConfig* ruc) |
|
190 | + | { |
|
191 | + | srs_error_t err = srs_success; |
|
192 | + | ||
172 | 193 | SrsSdp local_sdp; |
|
173 | 194 | ||
174 | 195 | // Config for SDP and session. |
|
175 | - | local_sdp.session_config_.dtls_role = _srs_config->get_rtc_dtls_role(ruc.req_->vhost); |
|
176 | - | local_sdp.session_config_.dtls_version = _srs_config->get_rtc_dtls_version(ruc.req_->vhost); |
|
196 | + | local_sdp.session_config_.dtls_role = _srs_config->get_rtc_dtls_role(ruc->req_->vhost); |
|
197 | + | local_sdp.session_config_.dtls_version = _srs_config->get_rtc_dtls_version(ruc->req_->vhost); |
|
177 | 198 | ||
178 | 199 | // Whether enabled. |
|
179 | 200 | bool server_enabled = _srs_config->get_rtc_server_enabled(); |
|
180 | - | bool rtc_enabled = _srs_config->get_rtc_enabled(ruc.req_->vhost); |
|
201 | + | bool rtc_enabled = _srs_config->get_rtc_enabled(ruc->req_->vhost); |
|
181 | 202 | if (server_enabled && !rtc_enabled) { |
|
182 | - | srs_warn("RTC disabled in vhost %s", ruc.req_->vhost.c_str()); |
|
203 | + | srs_warn("RTC disabled in vhost %s", ruc->req_->vhost.c_str()); |
|
183 | 204 | } |
|
184 | 205 | if (!server_enabled || !rtc_enabled) { |
|
185 | 206 | return srs_error_new(ERROR_RTC_DISABLED, "Disabled server=%d, rtc=%d, vhost=%s", |
|
186 | - | server_enabled, rtc_enabled, ruc.req_->vhost.c_str()); |
|
207 | + | server_enabled, rtc_enabled, ruc->req_->vhost.c_str()); |
|
187 | 208 | } |
|
188 | 209 | ||
189 | 210 | // Whether RTC stream is active. |
|
190 | 211 | bool is_rtc_stream_active = false; |
|
191 | 212 | if (true) { |
|
192 | - | SrsRtcSource* source = _srs_rtc_sources->fetch(ruc.req_); |
|
213 | + | SrsRtcSource* source = _srs_rtc_sources->fetch(ruc->req_); |
|
193 | 214 | is_rtc_stream_active = (source && !source->can_publish()); |
|
194 | 215 | } |
|
195 | 216 | ||
196 | 217 | // For RTMP to RTC, fail if disabled and RTMP is active, see https://github.com/ossrs/srs/issues/2728 |
|
197 | - | if (!is_rtc_stream_active && !_srs_config->get_rtc_from_rtmp(ruc.req_->vhost)) { |
|
198 | - | SrsLiveSource* rtmp = _srs_sources->fetch(ruc.req_); |
|
218 | + | if (!is_rtc_stream_active && !_srs_config->get_rtc_from_rtmp(ruc->req_->vhost)) { |
|
219 | + | SrsLiveSource* rtmp = _srs_sources->fetch(ruc->req_); |
|
199 | 220 | if (rtmp && !rtmp->inactive()) { |
|
200 | - | return srs_error_new(ERROR_RTC_DISABLED, "Disabled rtmp_to_rtc of %s, see #2728", ruc.req_->vhost.c_str()); |
|
221 | + | return srs_error_new(ERROR_RTC_DISABLED, "Disabled rtmp_to_rtc of %s, see #2728", ruc->req_->vhost.c_str()); |
|
201 | 222 | } |
|
202 | 223 | } |
|
203 | 224 | ||
204 | 225 | // TODO: FIXME: When server enabled, but vhost disabled, should report error. |
|
205 | 226 | SrsRtcConnection* session = NULL; |
|
206 | - | if ((err = server_->create_session(&ruc, local_sdp, &session)) != srs_success) { |
|
207 | - | return srs_error_wrap(err, "create session, dtls=%u, srtp=%u, eip=%s", ruc.dtls_, ruc.srtp_, eip.c_str()); |
|
227 | + | if ((err = server_->create_session(ruc, local_sdp, &session)) != srs_success) { |
|
228 | + | return srs_error_wrap(err, "create session, dtls=%u, srtp=%u, eip=%s", ruc->dtls_, ruc->srtp_, ruc->eip_.c_str()); |
|
208 | 229 | } |
|
209 | 230 | ||
210 | 231 | ostringstream os; |
216 | 237 | // Filter the \r\n to \\r\\n for JSON. |
|
217 | 238 | string local_sdp_escaped = srs_string_replace(local_sdp_str.c_str(), "\r\n", "\\r\\n"); |
|
218 | 239 | ||
219 | - | res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); |
|
220 | - | res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server_id().c_str())); |
|
221 | - | ||
222 | - | // TODO: add candidates in response json? |
|
223 | - | ||
224 | - | res->set("sdp", SrsJsonAny::str(local_sdp_str.c_str())); |
|
225 | - | res->set("sessionid", SrsJsonAny::str(session->username().c_str())); |
|
240 | + | ruc->local_sdp_str_ = local_sdp_str; |
|
241 | + | ruc->session_id_ = session->username(); |
|
226 | 242 | ||
227 | 243 | srs_trace("RTC username=%s, dtls=%u, srtp=%u, offer=%dB, answer=%dB", session->username().c_str(), |
|
228 | - | ruc.dtls_, ruc.srtp_, remote_sdp_str.length(), local_sdp_escaped.length()); |
|
229 | - | srs_trace("RTC remote offer: %s", srs_string_replace(remote_sdp_str.c_str(), "\r\n", "\\r\\n").c_str()); |
|
244 | + | ruc->dtls_, ruc->srtp_, ruc->remote_sdp_str_.length(), local_sdp_escaped.length()); |
|
245 | + | srs_trace("RTC remote offer: %s", srs_string_replace(ruc->remote_sdp_str_.c_str(), "\r\n", "\\r\\n").c_str()); |
|
230 | 246 | srs_trace("RTC local answer: %s", local_sdp_escaped.c_str()); |
|
231 | 247 | ||
232 | 248 | return err; |
333 | 349 | srs_error_t err = srs_success; |
|
334 | 350 | ||
335 | 351 | // For each RTC session, we use short-term HTTP connection. |
|
336 | - | SrsHttpHeader* hdr = w->header(); |
|
337 | - | hdr->set("Connection", "Close"); |
|
352 | + | w->header()->set("Connection", "Close"); |
|
338 | 353 | ||
339 | 354 | // Parse req, the request json object, from body. |
|
340 | 355 | SrsJsonObject* req = NULL; |
406 | 421 | ruc.req_->vhost = parsed_vhost->arg0(); |
|
407 | 422 | } |
|
408 | 423 | ||
409 | - | if ((err = http_hooks_on_publish(ruc.req_)) != srs_success) { |
|
410 | - | return srs_error_wrap(err, "RTC: http_hooks_on_publish"); |
|
411 | - | } |
|
412 | - | ||
413 | 424 | // For client to specifies the candidate(EIP) of server. |
|
414 | 425 | string eip = r->query_get("eip"); |
|
415 | 426 | if (eip.empty()) { |
428 | 439 | ruc.dtls_ = ruc.srtp_ = true; |
|
429 | 440 | ||
430 | 441 | // TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information. |
|
442 | + | ruc.remote_sdp_str_ = remote_sdp_str; |
|
431 | 443 | if ((err = ruc.remote_sdp_.parse(remote_sdp_str)) != srs_success) { |
|
432 | 444 | return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str()); |
|
433 | 445 | } |
|
434 | 446 | ||
435 | - | if ((err = check_remote_sdp(ruc.remote_sdp_)) != srs_success) { |
|
447 | + | if ((err = serve_http(w, r, &ruc)) != srs_success) { |
|
448 | + | return srs_error_wrap(err, "serve"); |
|
449 | + | } |
|
450 | + | ||
451 | + | res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); |
|
452 | + | res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server_id().c_str())); |
|
453 | + | ||
454 | + | // TODO: add candidates in response json? |
|
455 | + | res->set("sdp", SrsJsonAny::str(ruc.local_sdp_str_.c_str())); |
|
456 | + | res->set("sessionid", SrsJsonAny::str(ruc.session_id_.c_str())); |
|
457 | + | ||
458 | + | return err; |
|
459 | + | } |
|
460 | + | ||
461 | + | srs_error_t SrsGoApiRtcPublish::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsRtcUserConfig* ruc) |
|
462 | + | { |
|
463 | + | srs_error_t err = srs_success; |
|
464 | + | ||
465 | + | if ((err = check_remote_sdp(ruc->remote_sdp_)) != srs_success) { |
|
436 | 466 | return srs_error_wrap(err, "remote sdp check failed"); |
|
437 | 467 | } |
|
438 | 468 | ||
469 | + | if ((err = http_hooks_on_publish(ruc->req_)) != srs_success) { |
|
470 | + | return srs_error_wrap(err, "RTC: http_hooks_on_publish"); |
|
471 | + | } |
|
472 | + | ||
439 | 473 | SrsSdp local_sdp; |
|
440 | 474 | ||
441 | 475 | // TODO: FIXME: move to create_session. |
|
442 | 476 | // Config for SDP and session. |
|
443 | - | local_sdp.session_config_.dtls_role = _srs_config->get_rtc_dtls_role(ruc.req_->vhost); |
|
444 | - | local_sdp.session_config_.dtls_version = _srs_config->get_rtc_dtls_version(ruc.req_->vhost); |
|
477 | + | local_sdp.session_config_.dtls_role = _srs_config->get_rtc_dtls_role(ruc->req_->vhost); |
|
478 | + | local_sdp.session_config_.dtls_version = _srs_config->get_rtc_dtls_version(ruc->req_->vhost); |
|
445 | 479 | ||
446 | 480 | // Whether enabled. |
|
447 | 481 | bool server_enabled = _srs_config->get_rtc_server_enabled(); |
|
448 | - | bool rtc_enabled = _srs_config->get_rtc_enabled(ruc.req_->vhost); |
|
482 | + | bool rtc_enabled = _srs_config->get_rtc_enabled(ruc->req_->vhost); |
|
449 | 483 | if (server_enabled && !rtc_enabled) { |
|
450 | - | srs_warn("RTC disabled in vhost %s", ruc.req_->vhost.c_str()); |
|
484 | + | srs_warn("RTC disabled in vhost %s", ruc->req_->vhost.c_str()); |
|
451 | 485 | } |
|
452 | 486 | if (!server_enabled || !rtc_enabled) { |
|
453 | 487 | return srs_error_new(ERROR_RTC_DISABLED, "Disabled server=%d, rtc=%d, vhost=%s", |
|
454 | - | server_enabled, rtc_enabled, ruc.req_->vhost.c_str()); |
|
488 | + | server_enabled, rtc_enabled, ruc->req_->vhost.c_str()); |
|
455 | 489 | } |
|
456 | 490 | ||
457 | 491 | // TODO: FIXME: When server enabled, but vhost disabled, should report error. |
|
458 | 492 | SrsRtcConnection* session = NULL; |
|
459 | - | if ((err = server_->create_session(&ruc, local_sdp, &session)) != srs_success) { |
|
493 | + | if ((err = server_->create_session(ruc, local_sdp, &session)) != srs_success) { |
|
460 | 494 | return srs_error_wrap(err, "create session"); |
|
461 | 495 | } |
|
462 | 496 |
469 | 503 | // Filter the \r\n to \\r\\n for JSON. |
|
470 | 504 | string local_sdp_escaped = srs_string_replace(local_sdp_str.c_str(), "\r\n", "\\r\\n"); |
|
471 | 505 | ||
472 | - | res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); |
|
473 | - | res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server_id().c_str())); |
|
474 | - | ||
475 | - | // TODO: add candidates in response json? |
|
476 | - | ||
477 | - | res->set("sdp", SrsJsonAny::str(local_sdp_str.c_str())); |
|
478 | - | res->set("sessionid", SrsJsonAny::str(session->username().c_str())); |
|
506 | + | ruc->local_sdp_str_ = local_sdp_str; |
|
507 | + | ruc->session_id_ = session->username(); |
|
479 | 508 | ||
480 | 509 | srs_trace("RTC username=%s, offer=%dB, answer=%dB", session->username().c_str(), |
|
481 | - | remote_sdp_str.length(), local_sdp_escaped.length()); |
|
482 | - | srs_trace("RTC remote offer: %s", srs_string_replace(remote_sdp_str.c_str(), "\r\n", "\\r\\n").c_str()); |
|
510 | + | ruc->remote_sdp_str_.length(), local_sdp_escaped.length()); |
|
511 | + | srs_trace("RTC remote offer: %s", srs_string_replace(ruc->remote_sdp_str_.c_str(), "\r\n", "\\r\\n").c_str()); |
|
483 | 512 | srs_trace("RTC local answer: %s", local_sdp_escaped.c_str()); |
|
484 | 513 | ||
485 | 514 | return err; |
545 | 574 | return err; |
|
546 | 575 | } |
|
547 | 576 | ||
577 | + | SrsGoApiRtcWhip::SrsGoApiRtcWhip(SrsRtcServer* server) |
|
578 | + | { |
|
579 | + | publish_ = new SrsGoApiRtcPublish(server); |
|
580 | + | play_ = new SrsGoApiRtcPlay(server); |
|
581 | + | } |
|
582 | + | ||
583 | + | SrsGoApiRtcWhip::~SrsGoApiRtcWhip() |
|
584 | + | { |
|
585 | + | srs_freep(publish_); |
|
586 | + | srs_freep(play_); |
|
587 | + | } |
|
588 | + | ||
589 | + | srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) |
|
590 | + | { |
|
591 | + | srs_error_t err = srs_success; |
|
592 | + | ||
593 | + | // For each RTC session, we use short-term HTTP connection. |
|
594 | + | w->header()->set("Connection", "Close"); |
|
595 | + | ||
596 | + | string remote_sdp_str; |
|
597 | + | if ((err = r->body_read_all(remote_sdp_str)) != srs_success) { |
|
598 | + | return srs_error_wrap(err, "read sdp"); |
|
599 | + | } |
|
600 | + | ||
601 | + | string clientip; |
|
602 | + | if (clientip.empty()){ |
|
603 | + | clientip = dynamic_cast<SrsHttpMessage*>(r)->connection()->remote_ip(); |
|
604 | + | // Overwrite by ip from proxy. |
|
605 | + | string oip = srs_get_original_ip(r); |
|
606 | + | if (!oip.empty()) { |
|
607 | + | clientip = oip; |
|
608 | + | } |
|
609 | + | } |
|
610 | + | ||
611 | + | // For client to specifies the candidate(EIP) of server. |
|
612 | + | string eip = r->query_get("eip"); |
|
613 | + | if (eip.empty()) { |
|
614 | + | eip = r->query_get("candidate"); |
|
615 | + | } |
|
616 | + | string codec = r->query_get("codec"); |
|
617 | + | string app = r->query_get("app"); |
|
618 | + | string stream = r->query_get("stream"); |
|
619 | + | string action = r->query_get("action"); |
|
620 | + | if (action.empty()) { |
|
621 | + | action = "publish"; |
|
622 | + | } |
|
623 | + | if (srs_string_ends_with(r->path(), "/whip-play/")) { |
|
624 | + | action = "play"; |
|
625 | + | } |
|
626 | + | ||
627 | + | // The RTC user config object. |
|
628 | + | SrsRtcUserConfig ruc; |
|
629 | + | ruc.req_->ip = clientip; |
|
630 | + | ruc.req_->host = r->host(); |
|
631 | + | ruc.req_->vhost = ruc.req_->host; |
|
632 | + | ruc.req_->app = app.empty() ? "live" : app; |
|
633 | + | ruc.req_->stream = stream.empty() ? "livestream" : stream; |
|
634 | + | ||
635 | + | // discovery vhost, resolve the vhost from config |
|
636 | + | SrsConfDirective* parsed_vhost = _srs_config->get_vhost(ruc.req_->vhost); |
|
637 | + | if (parsed_vhost) { |
|
638 | + | ruc.req_->vhost = parsed_vhost->arg0(); |
|
639 | + | } |
|
640 | + | ||
641 | + | srs_trace("RTC whip %s %s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s", |
|
642 | + | action.c_str(), ruc.req_->get_stream_url().c_str(), clientip.c_str(), ruc.req_->app.c_str(), ruc.req_->stream.c_str(), |
|
643 | + | remote_sdp_str.length(), eip.c_str(), codec.c_str() |
|
644 | + | ); |
|
645 | + | ||
646 | + | ruc.eip_ = eip; |
|
647 | + | ruc.codec_ = codec; |
|
648 | + | ruc.publish_ = (action == "publish"); |
|
649 | + | ruc.dtls_ = ruc.srtp_ = true; |
|
650 | + | ||
651 | + | // TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information. |
|
652 | + | ruc.remote_sdp_str_ = remote_sdp_str; |
|
653 | + | if ((err = ruc.remote_sdp_.parse(remote_sdp_str)) != srs_success) { |
|
654 | + | return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str()); |
|
655 | + | } |
|
656 | + | ||
657 | + | err = action == "publish" ? publish_->serve_http(w, r, &ruc) : play_->serve_http(w, r, &ruc); |
|
658 | + | if (err != srs_success) { |
|
659 | + | return srs_error_wrap(err, "serve"); |
|
660 | + | } |
|
661 | + | ||
662 | + | if (ruc.local_sdp_str_.empty()) { |
|
663 | + | return srs_go_http_error(w, SRS_CONSTS_HTTP_InternalServerError); |
|
664 | + | } |
|
665 | + | ||
666 | + | string sdp = ruc.local_sdp_str_; |
|
667 | + | w->header()->set("Content-Type", "application/sdp"); |
|
668 | + | return w->write((char*)sdp.data(), (int)sdp.length()); |
|
669 | + | } |
|
670 | + | ||
548 | 671 | SrsGoApiRtcNACK::SrsGoApiRtcNACK(SrsRtcServer* server) |
|
549 | 672 | { |
|
550 | 673 | server_ = server; |
357 | 357 | class SrsVideoPayload : public SrsCodecPayload |
|
358 | 358 | { |
|
359 | 359 | public: |
|
360 | - | struct H264SpecificParameter |
|
361 | - | { |
|
362 | - | std::string profile_level_id; |
|
363 | - | std::string packetization_mode; |
|
364 | - | std::string level_asymmerty_allow; |
|
365 | - | }; |
|
366 | - | H264SpecificParameter h264_param_; |
|
360 | + | H264SpecificParam h264_param_; |
|
367 | 361 | ||
368 | 362 | public: |
|
369 | 363 | SrsVideoPayload(); |
290 | 290 | req_ = new SrsRequest(); |
|
291 | 291 | publish_ = false; |
|
292 | 292 | dtls_ = srtp_ = true; |
|
293 | + | audio_before_video_ = false; |
|
293 | 294 | } |
|
294 | 295 | ||
295 | 296 | SrsRtcUserConfig::~SrsRtcUserConfig() |
505 | 506 | return srs_error_wrap(err, "handle publish"); |
|
506 | 507 | } |
|
507 | 508 | ||
509 | + | // Generally, WHIP is a publishing protocol, but it can be also used as playing. |
|
510 | + | if ((err = http_api_mux->handle("/rtc/v1/whip/", new SrsGoApiRtcWhip(this))) != srs_success) { |
|
511 | + | return srs_error_wrap(err, "handle whip"); |
|
512 | + | } |
|
513 | + | ||
514 | + | // We create another mount, to support play with the same query string as publish. |
|
515 | + | if ((err = http_api_mux->handle("/rtc/v1/whip-play/", new SrsGoApiRtcWhip(this))) != srs_success) { |
|
516 | + | return srs_error_wrap(err, "handle whip play"); |
|
517 | + | } |
|
518 | + | ||
508 | 519 | #ifdef SRS_SIMULATOR |
|
509 | 520 | if ((err = http_api_mux->handle("/rtc/v1/nack/", new SrsGoApiRtcNACK(this))) != srs_success) { |
|
510 | 521 | return srs_error_wrap(err, "handle nack"); |
1992 | 1992 | return srs_error_wrap(err, "publish negotiate"); |
|
1993 | 1993 | } |
|
1994 | 1994 | ||
1995 | - | if ((err = generate_publish_local_sdp(req, local_sdp, stream_desc, ruc->remote_sdp_.is_unified())) != srs_success) { |
|
1995 | + | if ((err = generate_publish_local_sdp(req, local_sdp, stream_desc, ruc->remote_sdp_.is_unified(), ruc->audio_before_video_)) != srs_success) { |
|
1996 | 1996 | return srs_error_wrap(err, "generate local sdp"); |
|
1997 | 1997 | } |
|
1998 | 1998 |
2058 | 2058 | ++it; |
|
2059 | 2059 | } |
|
2060 | 2060 | ||
2061 | - | if ((err = generate_play_local_sdp(req, local_sdp, stream_desc, ruc->remote_sdp_.is_unified())) != srs_success) { |
|
2061 | + | if ((err = generate_play_local_sdp(req, local_sdp, stream_desc, ruc->remote_sdp_.is_unified(), ruc->audio_before_video_)) != srs_success) { |
|
2062 | 2062 | return srs_error_wrap(err, "generate local sdp"); |
|
2063 | 2063 | } |
|
2064 | 2064 |
2853 | 2853 | // TODO: FIME: Should check packetization-mode=1 also. |
|
2854 | 2854 | bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f"); |
|
2855 | 2855 | ||
2856 | + | // How many video descriptions we have parsed. |
|
2857 | + | int nn_any_video_parsed = 0; |
|
2858 | + | ||
2856 | 2859 | for (int i = 0; i < (int)remote_sdp.media_descs_.size(); ++i) { |
|
2857 | 2860 | const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_.at(i); |
|
2858 | 2861 | ||
2862 | + | if (remote_media_desc.is_video()) nn_any_video_parsed++; |
|
2863 | + | ||
2859 | 2864 | SrsRtcTrackDescription* track_desc = new SrsRtcTrackDescription(); |
|
2860 | 2865 | SrsAutoFree(SrsRtcTrackDescription, track_desc); |
|
2861 | 2866 |
2878 | 2883 | } |
|
2879 | 2884 | ||
2880 | 2885 | if (remote_media_desc.is_audio()) { |
|
2886 | + | // Update the ruc, which is about user specified configuration. |
|
2887 | + | ruc->audio_before_video_ = !nn_any_video_parsed; |
|
2888 | + | ||
2881 | 2889 | // TODO: check opus format specific param |
|
2882 | 2890 | std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("opus"); |
|
2883 | 2891 | if (payloads.empty()) { |
2977 | 2985 | SrsVideoPayload* video_payload = new SrsVideoPayload(payload.payload_type_, payload.encoding_name_, payload.clock_rate_); |
|
2978 | 2986 | video_payload->set_h264_param_desc(payload.format_specific_param_); |
|
2979 | 2987 | ||
2988 | + | // Set the codec parameter for H.264, to make Unity happy. |
|
2989 | + | video_payload->h264_param_ = h264_param; |
|
2990 | + | ||
2980 | 2991 | // TODO: FIXME: Only support some transport algorithms. |
|
2981 | 2992 | for (int k = 0; k < (int)payload.rtcp_fb_.size(); ++k) { |
|
2982 | 2993 | const string& rtcp_fb = payload.rtcp_fb_.at(k); |
3026 | 3037 | } |
|
3027 | 3038 | } |
|
3028 | 3039 | ||
3040 | + | track_desc->type_ = "video"; |
|
3029 | 3041 | track_desc->set_codec_payload((SrsCodecPayload*)video_payload); |
|
3030 | 3042 | srs_warn("choose backup H.264 payload type=%d", payload.payload_type_); |
|
3031 | 3043 | } |
3034 | 3046 | //local_media_desc.payload_types_.back().rtcp_fb_.push_back("rrtr"); |
|
3035 | 3047 | } |
|
3036 | 3048 | ||
3049 | + | // Error if track desc is invalid, that is failed to match SDP, for example, we require H264 but no H264 found. |
|
3050 | + | if (track_desc->type_.empty() || !track_desc->media_) { |
|
3051 | + | return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no match for track=%s, mid=%s, tracker=%s", remote_media_desc.type_.c_str(), remote_media_desc.mid_.c_str(), remote_media_desc.msid_tracker_.c_str()); |
|
3052 | + | } |
|
3053 | + | ||
3037 | 3054 | // TODO: FIXME: use one parse payload from sdp. |
|
3038 | 3055 | ||
3039 | 3056 | track_desc->create_auxiliary_payload(remote_media_desc.find_media_with_encoding_name("red")); |
3055 | 3072 | stream_desc->audio_track_desc_ = track_desc_copy; |
|
3056 | 3073 | } else if (remote_media_desc.is_video()) { |
|
3057 | 3074 | stream_desc->video_track_descs_.push_back(track_desc_copy); |
|
3075 | + | } else { |
|
3076 | + | srs_freep(track_desc_copy); |
|
3058 | 3077 | } |
|
3059 | 3078 | } |
|
3060 | 3079 | track_id = ssrc_info.msid_tracker_; |
3080 | 3099 | return err; |
|
3081 | 3100 | } |
|
3082 | 3101 | ||
3083 | - | srs_error_t SrsRtcConnection::generate_publish_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc, bool unified_plan) |
|
3102 | + | srs_error_t SrsRtcConnection::generate_publish_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc, bool unified_plan, bool audio_before_video) |
|
3084 | 3103 | { |
|
3085 | 3104 | srs_error_t err = srs_success; |
|
3086 | 3105 |
3105 | 3124 | ||
3106 | 3125 | local_sdp.group_policy_ = "BUNDLE"; |
|
3107 | 3126 | ||
3127 | + | if (audio_before_video) { |
|
3128 | + | if ((err = generate_publish_local_sdp_for_audio(local_sdp, stream_desc)) != srs_success) { |
|
3129 | + | return srs_error_wrap(err, "audio"); |
|
3130 | + | } |
|
3131 | + | if ((err = generate_publish_local_sdp_for_video(local_sdp, stream_desc, unified_plan)) != srs_success) { |
|
3132 | + | return srs_error_wrap(err, "video"); |
|
3133 | + | } |
|
3134 | + | } else { |
|
3135 | + | if ((err = generate_publish_local_sdp_for_video(local_sdp, stream_desc, unified_plan)) != srs_success) { |
|
3136 | + | return srs_error_wrap(err, "video"); |
|
3137 | + | } |
|
3138 | + | if ((err = generate_publish_local_sdp_for_audio(local_sdp, stream_desc)) != srs_success) { |
|
3139 | + | return srs_error_wrap(err, "audio"); |
|
3140 | + | } |
|
3141 | + | } |
|
3142 | + | ||
3143 | + | return err; |
|
3144 | + | } |
|
3145 | + | ||
3146 | + | srs_error_t SrsRtcConnection::generate_publish_local_sdp_for_audio(SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc) |
|
3147 | + | { |
|
3148 | + | srs_error_t err = srs_success; |
|
3149 | + | ||
3108 | 3150 | // generate audio media desc |
|
3109 | 3151 | if (stream_desc->audio_track_desc_) { |
|
3110 | 3152 | SrsRtcTrackDescription* audio_track = stream_desc->audio_track_desc_; |
3139 | 3181 | local_media_desc.payload_types_.push_back(payload->generate_media_payload_type()); |
|
3140 | 3182 | } |
|
3141 | 3183 | ||
3184 | + | return err; |
|
3185 | + | } |
|
3186 | + | ||
3187 | + | srs_error_t SrsRtcConnection::generate_publish_local_sdp_for_video(SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc, bool unified_plan) |
|
3188 | + | { |
|
3189 | + | srs_error_t err = srs_success; |
|
3190 | + | ||
3142 | 3191 | for (int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { |
|
3143 | 3192 | SrsRtcTrackDescription* video_track = stream_desc->video_track_descs_.at(i); |
|
3144 | 3193 |
3202 | 3251 | return srs_error_wrap(err, "fetch rtc source"); |
|
3203 | 3252 | } |
|
3204 | 3253 | ||
3254 | + | // How many video descriptions we have parsed. |
|
3255 | + | int nn_any_video_parsed = 0; |
|
3256 | + | ||
3205 | 3257 | for (int i = 0; i < (int)remote_sdp.media_descs_.size(); ++i) { |
|
3206 | 3258 | const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_.at(i); |
|
3207 | 3259 | ||
3260 | + | if (remote_media_desc.is_video()) nn_any_video_parsed++; |
|
3261 | + | ||
3208 | 3262 | // Whether feature enabled in remote extmap. |
|
3209 | 3263 | int remote_twcc_id = 0; |
|
3210 | 3264 | if (true) { |
3220 | 3274 | std::vector<SrsRtcTrackDescription*> track_descs; |
|
3221 | 3275 | SrsMediaPayloadType remote_payload(0); |
|
3222 | 3276 | if (remote_media_desc.is_audio()) { |
|
3277 | + | // Update the ruc, which is about user specified configuration. |
|
3278 | + | ruc->audio_before_video_ = !nn_any_video_parsed; |
|
3279 | + | ||
3223 | 3280 | // TODO: check opus format specific param |
|
3224 | 3281 | vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("opus"); |
|
3225 | 3282 | if (payloads.empty()) { |
3379 | 3436 | } |
|
3380 | 3437 | } |
|
3381 | 3438 | ||
3382 | - | srs_error_t SrsRtcConnection::generate_play_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc, bool unified_plan) |
|
3439 | + | srs_error_t SrsRtcConnection::generate_play_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc, bool unified_plan, bool audio_before_video) |
|
3383 | 3440 | { |
|
3384 | 3441 | srs_error_t err = srs_success; |
|
3385 | 3442 |
3406 | 3463 | ||
3407 | 3464 | std::string cname = srs_random_str(16); |
|
3408 | 3465 | ||
3466 | + | if (audio_before_video) { |
|
3467 | + | if ((err = generate_play_local_sdp_for_audio(local_sdp, stream_desc, cname)) != srs_success) { |
|
3468 | + | return srs_error_wrap(err, "audio"); |
|
3469 | + | } |
|
3470 | + | if ((err = generate_play_local_sdp_for_video(local_sdp, stream_desc, unified_plan, cname)) != srs_success) { |
|
3471 | + | return srs_error_wrap(err, "video"); |
|
3472 | + | } |
|
3473 | + | } else { |
|
3474 | + | if ((err = generate_play_local_sdp_for_video(local_sdp, stream_desc, unified_plan, cname)) != srs_success) { |
|
3475 | + | return srs_error_wrap(err, "video"); |
|
3476 | + | } |
|
3477 | + | if ((err = generate_play_local_sdp_for_audio(local_sdp, stream_desc, cname)) != srs_success) { |
|
3478 | + | return srs_error_wrap(err, "audio"); |
|
3479 | + | } |
|
3480 | + | } |
|
3481 | + | ||
3482 | + | return err; |
|
3483 | + | } |
|
3484 | + | ||
3485 | + | srs_error_t SrsRtcConnection::generate_play_local_sdp_for_audio(SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc, std::string cname) |
|
3486 | + | { |
|
3487 | + | srs_error_t err = srs_success; |
|
3488 | + | ||
3409 | 3489 | // generate audio media desc |
|
3410 | 3490 | if (stream_desc->audio_track_desc_) { |
|
3411 | 3491 | SrsRtcTrackDescription* audio_track = stream_desc->audio_track_desc_; |
3465 | 3545 | } |
|
3466 | 3546 | } |
|
3467 | 3547 | ||
3548 | + | return err; |
|
3549 | + | } |
|
3550 | + | ||
3551 | + | srs_error_t SrsRtcConnection::generate_play_local_sdp_for_video(SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc, bool unified_plan, std::string cname) |
|
3552 | + | { |
|
3553 | + | srs_error_t err = srs_success; |
|
3554 | + | ||
3468 | 3555 | for (int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { |
|
3469 | 3556 | SrsRtcTrackDescription* track = stream_desc->video_track_descs_[i]; |
|
3470 | 3557 |
56 | 56 | srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param) |
|
57 | 57 | { |
|
58 | 58 | srs_error_t err = srs_success; |
|
59 | - | std::vector<std::string> vec = split_str(fmtp, ";"); |
|
59 | + | ||
60 | + | std::vector<std::string> vec = srs_string_split(fmtp, ";"); |
|
60 | 61 | for (size_t i = 0; i < vec.size(); ++i) { |
|
61 | - | std::vector<std::string> kv = split_str(vec[i], "="); |
|
62 | - | if (kv.size() == 2) { |
|
63 | - | if (kv[0] == "profile-level-id") { |
|
64 | - | h264_param.profile_level_id = kv[1]; |
|
65 | - | } else if (kv[0] == "packetization-mode") { |
|
66 | - | // 6.3. Non-Interleaved Mode |
|
67 | - | // This mode is in use when the value of the OPTIONAL packetization-mode |
|
68 | - | // media type parameter is equal to 1. This mode SHOULD be supported. |
|
69 | - | // It is primarily intended for low-delay applications. Only single NAL |
|
70 | - | // unit packets, STAP-As, and FU-As MAY be used in this mode. STAP-Bs, |
|
71 | - | // MTAPs, and FU-Bs MUST NOT be used. The transmission order of NAL |
|
72 | - | // units MUST comply with the NAL unit decoding order. |
|
73 | - | // @see https://tools.ietf.org/html/rfc6184#section-6.3 |
|
74 | - | h264_param.packetization_mode = kv[1]; |
|
75 | - | } else if (kv[0] == "level-asymmetry-allowed") { |
|
76 | - | h264_param.level_asymmerty_allow = kv[1]; |
|
77 | - | } else { |
|
78 | - | return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h264 param=%s", kv[0].c_str()); |
|
79 | - | } |
|
80 | - | } else { |
|
81 | - | return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h264 param=%s", vec[i].c_str()); |
|
62 | + | std::vector<std::string> kv = srs_string_split(vec[i], "="); |
|
63 | + | if (kv.size() != 2) continue; |
|
64 | + | ||
65 | + | if (kv[0] == "profile-level-id") { |
|
66 | + | h264_param.profile_level_id = kv[1]; |
|
67 | + | } else if (kv[0] == "packetization-mode") { |
|
68 | + | // 6.3. Non-Interleaved Mode |
|
69 | + | // This mode is in use when the value of the OPTIONAL packetization-mode |
|
70 | + | // media type parameter is equal to 1. This mode SHOULD be supported. |
|
71 | + | // It is primarily intended for low-delay applications. Only single NAL |
|
72 | + | // unit packets, STAP-As, and FU-As MAY be used in this mode. STAP-Bs, |
|
73 | + | // MTAPs, and FU-Bs MUST NOT be used. The transmission order of NAL |
|
74 | + | // units MUST comply with the NAL unit decoding order. |
|
75 | + | // @see https://tools.ietf.org/html/rfc6184#section-6.3 |
|
76 | + | h264_param.packetization_mode = kv[1]; |
|
77 | + | } else if (kv[0] == "level-asymmetry-allowed") { |
|
78 | + | h264_param.level_asymmerty_allow = kv[1]; |
|
82 | 79 | } |
|
83 | 80 | } |
|
84 | 81 | ||
82 | + | if (h264_param.profile_level_id.empty()) { |
|
83 | + | return srs_error_new(ERROR_RTC_SDP_DECODE, "no h264 param: profile-level-id"); |
|
84 | + | } |
|
85 | + | if (h264_param.packetization_mode.empty()) { |
|
86 | + | return srs_error_new(ERROR_RTC_SDP_DECODE, "no h264 param: packetization-mode"); |
|
87 | + | } |
|
88 | + | if (h264_param.level_asymmerty_allow.empty()) { |
|
89 | + | return srs_error_new(ERROR_RTC_SDP_DECODE, "no h264 param: level-asymmetry-allowed"); |
|
90 | + | } |
|
91 | + | ||
85 | 92 | return err; |
|
86 | 93 | } |
|
87 | 94 |
Files | Coverage |
---|---|
trunk | -0.13% 58.46% |
Project Totals (133 files) | 58.46% |
686f577
#41
aea2bfb
8ac8ae1
1c0236a
#41
ef3347e
15610ca
e0c8c19