srs/trunk/src/app/srs_app_rtsp_conn.cpp
2025-09-27 09:40:57 -04:00

934 lines
30 KiB
C++

//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include <srs_app_rtsp_conn.hpp>
using namespace std;
#include <sys/socket.h>
#include <sstream>
#include <srs_app_config.hpp>
#include <srs_app_http_hooks.hpp>
#include <srs_app_rtsp_source.hpp>
#include <srs_app_security.hpp>
#include <srs_app_st.hpp>
#include <srs_app_statistic.hpp>
#include <srs_app_utility.hpp>
#include <srs_core_autofree.hpp>
#include <srs_kernel_buffer.hpp>
#include <srs_kernel_error.hpp>
#include <srs_kernel_kbps.hpp>
#include <srs_kernel_log.hpp>
#include <srs_kernel_pithy_print.hpp>
#include <srs_kernel_rtc_rtp.hpp>
#include <srs_protocol_rtsp_stack.hpp>
#include <srs_protocol_st.hpp>
#include <srs_protocol_utility.hpp>
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<uint32_t, SrsRtspAudioSendTrack *>::iterator it;
for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) {
srs_freep(it->second);
}
}
if (true) {
std::map<uint32_t, SrsRtspVideoSendTrack *>::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<uint32_t, SrsRtcTrackDescription *> 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<uint32_t, SrsRtcTrackDescription *>::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<SrsRtspSource> &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<SrsRtspConsumer> 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<SrsErrorPithyPrint> 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<uint32_t, SrsRtspAudioSendTrack *>::iterator it = audio_tracks_.find(ssrc);
if (it != audio_tracks_.end()) {
track = it->second;
}
} else {
map<uint32_t, SrsRtspVideoSendTrack *>::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<uint32_t, SrsRtspVideoSendTrack *>::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<uint32_t, SrsRtspAudioSendTrack *>::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<uint32_t, SrsRtcTrackDescription *>::iterator it = tracks_.begin(); it != tracks_.end(); ++it) {
srs_freep(it->second);
}
tracks_.clear();
for (std::map<uint32_t, ISrsStreamWriter *>::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<SrsRtspRequest> 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<SrsRtspOptionsResponse> 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<SrsRtspDescribeResponse> 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<SrsRtspSetupResponse> 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<SrsRtspResponse> 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<SrsRtspResponse> 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<SrsRtspConnection *>(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<SrsAudioPayload *>(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<std::string> 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<uint32_t, SrsRtcTrackDescription *>::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;
}