// // Copyright (c) 2013-2025 The SRS Authors // // SPDX-License-Identifier: MIT // #include using namespace std; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern SrsPps *_srs_pps_snack; extern SrsPps *_srs_pps_snack2; extern SrsPps *_srs_pps_snack3; extern SrsPps *_srs_pps_snack4; extern SrsPps *_srs_pps_rnack; extern SrsPps *_srs_pps_rnack2; extern SrsPps *_srs_pps_pub; extern SrsPps *_srs_pps_conn; SrsRtspPlayStream::SrsRtspPlayStream(SrsRtspConnection *s, const SrsContextId &cid) : source_(new SrsRtspSource()) { cid_ = cid; trd_ = NULL; req_ = NULL; is_started = false; session_ = s; cache_ssrc0_ = cache_ssrc1_ = cache_ssrc2_ = 0; cache_track0_ = cache_track1_ = cache_track2_ = NULL; } SrsRtspPlayStream::~SrsRtspPlayStream() { srs_freep(trd_); srs_freep(req_); if (true) { std::map::iterator it; for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { srs_freep(it->second); } } if (true) { std::map::iterator it; for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { srs_freep(it->second); } } // update the statistic when client coveried. SrsStatistic *stat = _srs_stat; // TODO: FIXME: Should finger out the err. stat->on_disconnect(cid_.c_str(), srs_success); } srs_error_t SrsRtspPlayStream::initialize(ISrsRequest *req, std::map sub_relations) { srs_error_t err = srs_success; req_ = req->copy(); // We must do stat the client before hooks, because hooks depends on it. SrsStatistic *stat = _srs_stat; if ((err = stat->on_client(cid_.c_str(), req_, session_, SrsRtcConnPlay)) != srs_success) { return srs_error_wrap(err, "RTSP: stat client"); } if ((err = _srs_rtsp_sources->fetch_or_create(req_, source_)) != srs_success) { return srs_error_wrap(err, "RTSP: fetch source failed"); } for (map::iterator it = sub_relations.begin(); it != sub_relations.end(); ++it) { uint32_t ssrc = it->first; SrsRtcTrackDescription *desc = it->second; if (desc->type_ == "audio") { SrsRtspAudioSendTrack *track = new SrsRtspAudioSendTrack(session_, desc); audio_tracks_.insert(make_pair(ssrc, track)); } if (desc->type_ == "video") { SrsRtspVideoSendTrack *track = new SrsRtspVideoSendTrack(session_, desc); video_tracks_.insert(make_pair(ssrc, track)); } } return err; } // TODO: Remove it for RTSP? void SrsRtspPlayStream::on_stream_change(SrsRtcSourceDescription *desc) { if (!desc) return; // Refresh the relation for audio. // TODO: FIXME: Match by label? if (desc && desc->audio_track_desc_ && audio_tracks_.size() == 1) { if (!audio_tracks_.empty()) { uint32_t ssrc = desc->audio_track_desc_->ssrc_; SrsRtspAudioSendTrack *track = audio_tracks_.begin()->second; if (track->track_desc_->media_->pt_of_publisher_ != desc->audio_track_desc_->media_->pt_) { track->track_desc_->media_->pt_of_publisher_ = desc->audio_track_desc_->media_->pt_; } if (desc->audio_track_desc_->red_ && track->track_desc_->red_ && track->track_desc_->red_->pt_of_publisher_ != desc->audio_track_desc_->red_->pt_) { track->track_desc_->red_->pt_of_publisher_ = desc->audio_track_desc_->red_->pt_; } audio_tracks_.clear(); audio_tracks_.insert(make_pair(ssrc, track)); } } // Refresh the relation for video. // TODO: FIMXE: Match by label? if (desc && desc->video_track_descs_.size() == 1) { if (!video_tracks_.empty()) { SrsRtcTrackDescription *vdesc = desc->video_track_descs_.at(0); uint32_t ssrc = vdesc->ssrc_; SrsRtspVideoSendTrack *track = video_tracks_.begin()->second; if (track->track_desc_->media_->pt_of_publisher_ != vdesc->media_->pt_) { track->track_desc_->media_->pt_of_publisher_ = vdesc->media_->pt_; } if (vdesc->red_ && track->track_desc_->red_ && track->track_desc_->red_->pt_of_publisher_ != vdesc->red_->pt_) { track->track_desc_->red_->pt_of_publisher_ = vdesc->red_->pt_; } video_tracks_.clear(); video_tracks_.insert(make_pair(ssrc, track)); } } } const SrsContextId &SrsRtspPlayStream::context_id() { return cid_; } srs_error_t SrsRtspPlayStream::start() { srs_error_t err = srs_success; // If player coroutine allocated, we think the player is started. // To prevent play multiple times for this play stream. // @remark Allow start multiple times, for DTLS may retransmit the final packet. if (is_started) { return err; } srs_freep(trd_); trd_ = new SrsFastCoroutine("rtsp_sender", this, cid_); if ((err = trd_->start()) != srs_success) { return srs_error_wrap(err, "rtsp_sender"); } is_started = true; return err; } void SrsRtspPlayStream::stop() { if (trd_) { trd_->stop(); } } srs_error_t SrsRtspPlayStream::cycle() { srs_error_t err = srs_success; SrsSharedPtr &source = source_; srs_assert(source.get()); SrsRtspConsumer *consumer_raw = NULL; if ((err = source->create_consumer(consumer_raw)) != srs_success) { return srs_error_wrap(err, "create consumer, source=%s", req_->get_stream_url().c_str()); } srs_assert(consumer_raw); SrsUniquePtr consumer(consumer_raw); consumer->set_handler(this); // TODO: FIXME: Dumps the SPS/PPS from gop cache, without other frames. if ((err = source->consumer_dumps(consumer.get())) != srs_success) { return srs_error_wrap(err, "dumps consumer, url=%s", req_->get_stream_url().c_str()); } // TODO: FIXME: Add cost in ms. SrsContextId cid = source->source_id(); srs_trace("RTSP: start play url=%s, source_id=%s/%s", req_->get_stream_url().c_str(), cid.c_str(), source->pre_source_id().c_str()); SrsUniquePtr epp(new SrsErrorPithyPrint()); // For RTSP, donot use merged write. int mw_msgs = 1; while (true) { if ((err = trd_->pull()) != srs_success) { return srs_error_wrap(err, "RTSP sender thread"); } // Wait for amount of packets. SrsRtpPacket *pkt = NULL; consumer->dump_packet(&pkt); if (!pkt) { // TODO: FIXME: We should check the quit event. consumer->wait(mw_msgs); continue; } // Send-out the RTP packet and do cleanup // @remark Note that the pkt might be set to NULL. if ((err = send_packet(pkt)) != srs_success) { uint32_t nn = 0; if (epp->can_print(err, &nn)) { srs_warn("play send packets=%u, nn=%u/%u, err: %s", 1, epp->nn_count_, nn, srs_error_desc(err).c_str()); } srs_freep(err); } // Free the packet. // @remark Note that the pkt might be set to NULL. srs_freep(pkt); } } srs_error_t SrsRtspPlayStream::send_packet(SrsRtpPacket *&pkt) { srs_error_t err = srs_success; uint32_t ssrc = pkt->header_.get_ssrc(); // Try to find track from cache. SrsRtspSendTrack *track = NULL; if (cache_ssrc0_ == ssrc) { track = cache_track0_; } else if (cache_ssrc1_ == ssrc) { track = cache_track1_; } else if (cache_ssrc2_ == ssrc) { track = cache_track2_; } // Find by original tracks and build fast cache. if (!track) { if (pkt->is_audio()) { map::iterator it = audio_tracks_.find(ssrc); if (it != audio_tracks_.end()) { track = it->second; } } else { map::iterator it = video_tracks_.find(ssrc); if (it != video_tracks_.end()) { track = it->second; } } if (track && !cache_ssrc2_) { if (!cache_ssrc0_) { cache_ssrc0_ = ssrc; cache_track0_ = track; } else if (!cache_ssrc1_) { cache_ssrc1_ = ssrc; cache_track1_ = track; } else if (!cache_ssrc2_) { cache_ssrc2_ = ssrc; cache_track2_ = track; } } } // Ignore if no track found. if (!track) { srs_warn("RTSP: Drop for ssrc %u not found", ssrc); return err; } // Consume packet by track. if ((err = track->on_rtp(pkt)) != srs_success) { return srs_error_wrap(err, "audio track, SSRC=%u, SEQ=%u", ssrc, pkt->header_.get_sequence()); } return err; } void SrsRtspPlayStream::set_all_tracks_status(bool status) { std::ostringstream merged_log; // set video track status if (true) { std::map::iterator it; for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { SrsRtspVideoSendTrack *track = it->second; bool previous = track->set_track_status(status); merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; } } // set audio track status if (true) { std::map::iterator it; for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { SrsRtspAudioSendTrack *track = it->second; bool previous = track->set_track_status(status); merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; } } srs_trace("RTSP: Init tracks %s ok", merged_log.str().c_str()); } SrsRtspConnection::SrsRtspConnection(ISrsResourceManager *cm, ISrsProtocolReadWriter *skt, std::string cip, int port) { manager_ = cm; cid_ = _srs_context->generate_id(); _srs_context->set_id(cid_); // Initialize timeout management fields from SrsRtspConnection2 last_stun_time = 0; session_timeout = 0; disposing_ = false; request_ = new SrsRequest(); request_->ip_ = cip; ip_ = cip; port_ = port; rtsp_ = new SrsRtspStack(skt); trd_ = new SrsSTCoroutine("rtsp", this, _srs_context->get_id()); // Initialize merged SrsRtspSession members skt_ = skt; source_ = NULL; player_ = NULL; cache_iov_ = new iovec(); cache_iov_->iov_base = new char[kRtpPacketSize]; cache_iov_->iov_len = kRtpPacketSize; cache_buffer_ = new SrsBuffer((char *)cache_iov_->iov_base, kRtpPacketSize); delta_ = new SrsEphemeralDelta(); security_ = new SrsSecurity(); _srs_rtsp_manager->subscribe(this); } SrsRtspConnection::~SrsRtspConnection() { _srs_rtsp_manager->unsubscribe(this); srs_freep(request_); srs_freep(rtsp_); srs_freep(trd_); // Cleanup merged SrsRtspSession members for (std::map::iterator it = tracks_.begin(); it != tracks_.end(); ++it) { srs_freep(it->second); } tracks_.clear(); for (std::map::iterator it = networks_.begin(); it != networks_.end(); ++it) { srs_freep(it->second); } networks_.clear(); srs_freep(delta_); srs_freep(security_); srs_freep(player_); if (true) { char *iov_base = (char *)cache_iov_->iov_base; srs_freepa(iov_base); srs_freep(cache_iov_); } srs_freep(cache_buffer_); } srs_error_t SrsRtspConnection::do_send_packet(SrsRtpPacket *pkt) { srs_error_t err = srs_success; uint32_t ssrc = pkt->header_.get_ssrc(); ISrsStreamWriter *network = networks_[ssrc]; if (!network) { return srs_error_new(ERROR_RTSP_NO_TRACK, "network not found for ssrc: %u", ssrc); } iovec *iov = cache_iov_; cache_buffer_->skip(-1 * cache_buffer_->pos()); // Marshal packet to bytes in iovec. if (true) { if ((err = pkt->encode(cache_buffer_)) != srs_success) { return srs_error_wrap(err, "encode packet"); } iov->iov_len = cache_buffer_->pos(); } ssize_t write = 0; if ((err = network->write(iov->iov_base, iov->iov_len, &write)) != srs_success) { return srs_error_wrap(err, "send rtp packet"); } delta_->add_delta(0, write); return err; } ISrsKbpsDelta *SrsRtspConnection::delta() { return delta_; } std::string SrsRtspConnection::desc() { return "Rtsp"; } const SrsContextId &SrsRtspConnection::get_id() { return cid_; } std::string SrsRtspConnection::remote_ip() { return ip_; } void SrsRtspConnection::expire() { trd_->interrupt(); } srs_error_t SrsRtspConnection::start() { srs_error_t err = srs_success; if ((err = trd_->start()) != srs_success) { return srs_error_wrap(err, "coroutine"); } return err; } srs_error_t SrsRtspConnection::cycle() { srs_error_t err = srs_success; // Serve the client. err = do_cycle(); // Update statistic when done. SrsStatistic *stat = _srs_stat; stat->kbps_add_delta(get_id().c_str(), delta()); do_teardown(); // Notify manager to remove it. // Note that we create this object, so we use manager to remove it. manager_->remove(this); // success. if (err == srs_success) { srs_trace("RTSP: client finished."); return err; } // It maybe success with message. if (srs_error_code(err) == ERROR_SUCCESS) { srs_trace("RTSP: client finished%s.", srs_error_summary(err).c_str()); srs_freep(err); return err; } // client close peer. // TODO: FIXME: Only reset the error when client closed it. if (srs_is_client_gracefully_close(err)) { srs_warn("RTSP: client disconnect peer. ret=%d", srs_error_code(err)); } else if (srs_is_server_gracefully_close(err)) { srs_warn("RTSP: server disconnect. ret=%d", srs_error_code(err)); } else { srs_error("RTSP: serve error %s", srs_error_desc(err).c_str()); } srs_freep(err); return srs_success; } srs_error_t SrsRtspConnection::do_cycle() { srs_error_t err = srs_success; srs_trace("RTSP: client ip=%s, port=%d", ip_.c_str(), port_); // consume all rtsp messages. while (true) { if ((err = trd_->pull()) != srs_success) { return srs_error_wrap(err, "rtsp cycle"); } SrsRtspRequest *req_raw = NULL; if ((err = rtsp_->recv_message(&req_raw)) != srs_success) { return srs_error_wrap(err, "recv message"); } SrsUniquePtr req(req_raw); if (req->is_options()) { srs_trace("RTSP: OPTIONS cseq=%ld, url=%s, client=%s:%d", req->seq_, req->uri_.c_str(), ip_.c_str(), port_); SrsUniquePtr res(new SrsRtspOptionsResponse((int)req->seq_)); if ((err = rtsp_->send_message(res.get())) != srs_success) { return srs_error_wrap(err, "response option"); } } else if (req->is_describe()) { // create session. if (session_id_.empty()) { session_id_ = srs_rand_gen_str(8); } SrsUniquePtr res(new SrsRtspDescribeResponse((int)req->seq_)); res->session_ = session_id_; std::string sdp; if ((err = do_describe(req.get(), sdp)) != srs_success) { res->status_ = SRS_CONSTS_RTSP_InternalServerError; if (srs_error_code(err) == ERROR_RTSP_NO_TRACK) { res->status_ = SRS_CONSTS_RTSP_NotFound; } else if (srs_error_code(err) == ERROR_SYSTEM_SECURITY_DENY) { res->status_ = SRS_CONSTS_RTSP_Forbidden; } srs_warn("RTSP: DESCRIBE failed: %s", srs_error_desc(err).c_str()); srs_freep(err); } res->sdp_ = sdp; if ((err = rtsp_->send_message(res.get())) != srs_success) { return srs_error_wrap(err, "response describe"); } // Filter the \r\n to \\r\\n for JSON. std::string local_sdp_escaped = srs_strings_replace(sdp.c_str(), "\r\n", "\\r\\n"); srs_trace("RTSP: DESCRIBE cseq=%ld, session=%s, sdp: %s", req->seq_, session_id_.c_str(), local_sdp_escaped.c_str()); } else if (req->is_setup()) { srs_assert(req->transport_); SrsUniquePtr res(new SrsRtspSetupResponse((int)req->seq_)); res->session_ = session_id_; uint32_t ssrc = 0; if ((err = do_setup(req.get(), &ssrc)) != srs_success) { if (srs_error_code(err) == ERROR_RTSP_TRANSPORT_NOT_SUPPORTED) { res->status_ = SRS_CONSTS_RTSP_UnsupportedTransport; srs_warn("RTSP: SETUP failed: %s", srs_error_summary(err).c_str()); } else { res->status_ = SRS_CONSTS_RTSP_InternalServerError; srs_warn("RTSP: SETUP failed: %s", srs_error_desc(err).c_str()); } srs_freep(err); } res->transport_->copy(req->transport_); res->session_ = session_id_; res->ssrc_ = srs_strconv_format_int(ssrc); res->client_port_min_ = req->transport_->client_port_min_; res->client_port_max_ = req->transport_->client_port_max_; // TODO: FIXME: listen local port res->local_port_min_ = 0; res->local_port_max_ = 0; if ((err = rtsp_->send_message(res.get())) != srs_success) { return srs_error_wrap(err, "response setup"); } srs_trace("RTSP: SETUP cseq=%ld, session=%s, transport=%s/%s/%s, ssrc=%u, client_port=%d-%d", req->seq_, session_id_.c_str(), req->transport_->transport_.c_str(), req->transport_->profile_.c_str(), req->transport_->lower_transport_.c_str(), ssrc, req->transport_->client_port_min_, req->transport_->client_port_max_); } else if (req->is_play()) { SrsUniquePtr res(new SrsRtspResponse((int)req->seq_)); res->session_ = session_id_; if ((err = rtsp_->send_message(res.get())) != srs_success) { return srs_error_wrap(err, "response record"); } if ((err = do_play(req.get(), this)) != srs_success) { return srs_error_wrap(err, "prepare play"); } srs_trace("RTSP: PLAY cseq=%ld, session=%s, streaming started", req->seq_, session_id_.c_str()); } else if (req->is_teardown()) { SrsUniquePtr res(new SrsRtspResponse((int)req->seq_)); res->session_ = session_id_; if ((err = rtsp_->send_message(res.get())) != srs_success) { return srs_error_wrap(err, "response teardown"); } if ((err = do_teardown()) != srs_success) { return srs_error_wrap(err, "teardown"); } srs_trace("RTSP: TEARDOWN cseq=%ld, session=%s, streaming stopped", req->seq_, session_id_.c_str()); } } return err; } void SrsRtspConnection::on_before_dispose(ISrsResource *c) { if (disposing_) { return; } SrsRtspConnection *session = dynamic_cast(c); if (session == this) { disposing_ = true; } if (session && session == this) { _srs_context->set_id(cid_); srs_trace("RTSP: session detach from [%s](%s), disposing=%d", c->get_id().c_str(), c->desc().c_str(), disposing_); } } void SrsRtspConnection::on_disposing(ISrsResource *c) { if (disposing_) { return; } } void SrsRtspConnection::switch_to_context() { _srs_context->set_id(cid_); } const SrsContextId &SrsRtspConnection::context_id() { return cid_; } bool SrsRtspConnection::is_alive() { return last_stun_time + session_timeout > srs_time_now_cached(); } void SrsRtspConnection::alive() { last_stun_time = srs_time_now_cached(); } srs_error_t SrsRtspConnection::do_describe(SrsRtspRequest *req, std::string &sdp) { srs_error_t err = srs_success; srs_net_url_parse_rtmp_url(req->uri_, request_->tcUrl_, request_->stream_); srs_net_url_parse_tcurl(request_->tcUrl_, request_->schema_, request_->host_, request_->vhost_, request_->app_, request_->stream_, request_->port_, request_->param_); // discovery vhost, resolve the vhost from config SrsConfDirective *parsed_vhost = _srs_config->get_vhost(request_->vhost_); if (parsed_vhost) { request_->vhost_ = parsed_vhost->arg0(); } if ((err = security_->check(SrsRtcConnPlay, ip_, request_)) != srs_success) { return srs_error_wrap(err, "RTSP: security check"); } if ((err = http_hooks_on_play(request_)) != srs_success) { return srs_error_wrap(err, "RTSP: http_hooks_on_play"); } if ((err = _srs_rtsp_sources->fetch_or_create(request_, source_)) != srs_success) { return srs_error_wrap(err, "create source"); } SrsSdp local_sdp; local_sdp.version_ = "0"; local_sdp.username_ = "SRS RTSP Server"; local_sdp.session_id_ = "0"; local_sdp.session_version_ = "0"; local_sdp.nettype_ = "IN"; local_sdp.addrtype_ = "IP4"; local_sdp.unicast_address_ = "0.0.0.0"; local_sdp.session_name_ = "Play"; local_sdp.control_ = req->uri_; local_sdp.ice_lite_ = ""; // Disable this line. uint32_t track_id = 0; SrsRtcTrackDescription *audio_desc = source_->audio_desc(); if (audio_desc) { SrsRtcTrackDescription *audio_track_desc = audio_desc->copy(); audio_track_desc->id_ = srs_strconv_format_int(track_id); tracks_.insert(std::make_pair(audio_track_desc->ssrc_, audio_track_desc)); SrsMediaDesc media_audio("audio"); media_audio.port_ = 0; // Port 0 indicates no UDP transport available media_audio.protos_ = "RTP/AVP"; // MUST be RTP/AVP media_audio.control_ = req->uri_ + "/trackID=" + srs_strconv_format_int(track_id); media_audio.recvonly_ = true; media_audio.rtcp_mux_ = true; media_audio.payload_types_.push_back(SrsMediaPayloadType(audio_track_desc->media_->pt_)); SrsMediaPayloadType &ps_audio = media_audio.payload_types_.at(0); ps_audio.encoding_name_ = audio_track_desc->media_->name_; ps_audio.clock_rate_ = audio_track_desc->media_->sample_; // if the payload is opus, and the encoding_param_ is channel SrsAudioPayload *ap = dynamic_cast(audio_track_desc->media_); if (ap) { ps_audio.encoding_param_ = srs_strconv_format_int(ap->channel_); // Append the AAC config hex to the fmtp line. if (ap->name_ == "MPEG4-GENERIC" && !ap->aac_config_hex_.empty()) { // streamtype=5 - Mandatory (indicates audio stream) // mode=AAC-hbr - Mandatory (AAC High Bit Rate mode) // sizelength=13 - Mandatory, defaults to 13 bits for AAC // indexlength=3 - Mandatory, defaults to 3 bits // profile-level-id=1 - Optional, defaults to 1 (AAC Main Profile) // indexdeltalength=3 - Optional, defaults to 3 bits ps_audio.format_specific_param_ = "streamtype=5;mode=AAC-hbr;sizelength=13;indexlength=3"; ps_audio.format_specific_param_ += ";config=" + ap->aac_config_hex_; srs_trace("RTSP: Added AAC fmtp: %s", ps_audio.format_specific_param_.c_str()); } } local_sdp.media_descs_.push_back(media_audio); track_id++; } SrsRtcTrackDescription *video_desc = source_->video_desc(); if (video_desc) { SrsRtcTrackDescription *video_track_desc = video_desc->copy(); video_track_desc->id_ = srs_strconv_format_int(track_id); tracks_.insert(std::make_pair(video_track_desc->ssrc_, video_track_desc)); SrsMediaDesc media_video("video"); media_video.port_ = 0; // Port 0 indicates no UDP transport available media_video.protos_ = "RTP/AVP"; // MUST be RTP/AVP media_video.control_ = req->uri_ + "/trackID=" + srs_strconv_format_int(track_id); media_video.recvonly_ = true; media_video.rtcp_mux_ = true; media_video.payload_types_.push_back(SrsMediaPayloadType(video_track_desc->media_->pt_)); SrsMediaPayloadType &ps_video = media_video.payload_types_.at(0); ps_video.encoding_name_ = video_track_desc->media_->name_; ps_video.clock_rate_ = video_track_desc->media_->sample_; local_sdp.media_descs_.push_back(media_video); track_id++; } if (track_id == 0) { return srs_error_new(ERROR_RTSP_NO_TRACK, "no track found"); } std::ostringstream ss; if ((err = local_sdp.encode(ss)) != srs_success) { return srs_error_wrap(err, "encode sdp"); } sdp = ss.str(); return srs_success; } srs_error_t SrsRtspConnection::do_setup(SrsRtspRequest *req, uint32_t *pssrc) { srs_error_t err = srs_success; uint32_t ssrc = 0; if ((err = get_ssrc_by_stream_id(req->stream_id_, &ssrc)) != srs_success) { return srs_error_wrap(err, "get ssrc by stream_id"); } // Only support TCP transport, reject UDP // This ensures better firewall/NAT compatibility and eliminates port allocation complexity if (req->transport_->lower_transport_ != "TCP") { return srs_error_new(ERROR_RTSP_TRANSPORT_NOT_SUPPORTED, "UDP transport not supported, only TCP/interleaved mode is supported"); } SrsRtspTcpNetwork *network = new SrsRtspTcpNetwork(skt_, req->transport_->interleaved_min_); networks_[ssrc] = network; *pssrc = ssrc; return srs_success; } srs_error_t SrsRtspConnection::do_play(SrsRtspRequest *req, SrsRtspConnection *conn) { srs_error_t err = srs_success; srs_freep(player_); player_ = new SrsRtspPlayStream(conn, cid_); if ((err = player_->initialize(request_, tracks_)) != srs_success) { srs_freep(player_); return srs_error_wrap(err, "SrsRtspPlayStream init"); } player_->set_all_tracks_status(true); if ((err = player_->start()) != srs_success) { return srs_error_wrap(err, "start play"); } srs_trace("RTSP: Subscriber url=%s established", req->uri_.c_str()); return err; } srs_error_t SrsRtspConnection::do_teardown() { if (player_) { player_->stop(); srs_freep(player_); } return srs_success; } srs_error_t SrsRtspConnection::http_hooks_on_play(ISrsRequest *req) { srs_error_t err = srs_success; if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost_)) { return err; } // the http hooks will cause context switch, // so we must copy all hooks for the on_connect may freed. // @see https://github.com/ossrs/srs/issues/475 std::vector hooks; if (true) { SrsConfDirective *conf = _srs_config->get_vhost_on_play(req->vhost_); if (!conf) { return err; } hooks = conf->args_; } for (int i = 0; i < (int)hooks.size(); i++) { std::string url = hooks.at(i); if ((err = _srs_hooks->on_play(url, req)) != srs_success) { return srs_error_wrap(err, "on_play %s", url.c_str()); } } return err; } srs_error_t SrsRtspConnection::get_ssrc_by_stream_id(uint32_t stream_id, uint32_t *ssrc) { for (std::map::iterator it = tracks_.begin(); it != tracks_.end(); ++it) { if (it->second->id_ == srs_strconv_format_int(stream_id)) { *ssrc = it->second->ssrc_; return srs_success; } } return srs_error_new(ERROR_RTSP_NO_TRACK, "track not found for stream_id: %u", stream_id); } SrsRtspTcpNetwork::SrsRtspTcpNetwork(ISrsProtocolReadWriter *skt, int ch) : skt_(skt), channel_(ch) { } SrsRtspTcpNetwork::~SrsRtspTcpNetwork() { } srs_error_t SrsRtspTcpNetwork::write(void *buf, size_t size, ssize_t *nwrite) { srs_error_t err = srs_success; srs_assert(size <= 65535); // Encode and send 4 bytes size, in network order. const int kRtpTcpPacketHeaderSize = 4; char header[kRtpTcpPacketHeaderSize]; // Use SrsBuffer to handle endianness properly SrsBuffer hb(header, kRtpTcpPacketHeaderSize); hb.write_1bytes(0x24); // Magic byte '$' hb.write_1bytes(uint8_t(channel_)); // Channel number hb.write_2bytes(uint16_t(size)); // Packet size in network order if ((err = skt_->write(header, kRtpTcpPacketHeaderSize, NULL)) != srs_success) { return srs_error_wrap(err, "RTSP tcp write len(%d)", size); } if ((err = skt_->write(buf, size, nwrite)) != srs_success) { return srs_error_wrap(err, "RTSP send rtp packet"); } // Add the size of the header to the write count. *nwrite += kRtpTcpPacketHeaderSize; return err; }