diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index db3965000..0579788fa 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -676,9 +676,11 @@ stream_caster { listen 9000; # SIP server for GB28181. Please note that this is only a demonstrated SIP server, please never use it in your # online production environment. Instead please use [jsip](https://github.com/usnistgov/jsip) and there is a demo - # [srs-sip](https://github.com/ossrs/srs-sip) also base on it. + # [srs-sip](https://github.com/ossrs/srs-sip) also base on it, for more information please see project + # [GB: External SIP](https://ossrs.net/lts/zh-cn/docs/v6/doc/gb28181#external-sip). sip { - # Whether enable embedded SIP server. + # Whether enable embedded SIP server. Please disable it if you want to use your own SIP server, see + # [GB: External SIP](https://ossrs.net/lts/zh-cn/docs/v6/doc/gb28181#external-sip). # Default: on enabled on; # The SIP listen port, for TCP protocol. diff --git a/trunk/conf/gb28181-without-sip.conf b/trunk/conf/gb28181-without-sip.conf new file mode 100644 index 000000000..1b19326b4 --- /dev/null +++ b/trunk/conf/gb28181-without-sip.conf @@ -0,0 +1,53 @@ + +listen 1935; +max_connections 1000; +daemon off; +srs_log_tank console; + +stream_caster { + enabled on; + caster gb28181; + output rtmp://127.0.0.1/live/[stream]; + listen 9000; + sip { + enabled off; + } +} + +http_server { + enabled on; + listen 8080; + dir ./objs/nginx/html; +} + +http_api { + enabled on; + listen 1985; +} +stats { + network 0; +} +rtc_server { + enabled on; + listen 8000; # UDP port + # @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate + candidate $CANDIDATE; +} + +vhost __defaultVhost__ { + rtc { + enabled on; + # @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc + rtmp_to_rtc on; + # @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp + rtc_to_rtmp on; + } + http_remux { + enabled on; + mount [vhost]/[app]/[stream].flv; + } + hls { + enabled on; + } +} + diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 693169446..ce56b82aa 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 6.0 Changelog +* v6.0, 2024-07-27, Merge [#4101](https://github.com/ossrs/srs/pull/4101): GB28181: Support external SIP server. v6.0.144 (#4101) * v6.0, 2024-07-24, Merge [#4115](https://github.com/ossrs/srs/pull/4115): HLS: Add missing newline to end of session manifest. v6.0.143 (#4115) * v6.0, 2024-07-24, Merge [#4029](https://github.com/ossrs/srs/pull/4029): Player: Fix empty img tag occupy 20px size in safari. v6.0.142 (#4029) * v6.0, 2024-07-24, Merge [#4063](https://github.com/ossrs/srs/pull/4063): let http-remux ts stream support guess_has_av feature;. v6.0.141 (#4063) diff --git a/trunk/src/app/srs_app_gb28181.cpp b/trunk/src/app/srs_app_gb28181.cpp index d412a61cb..abe1eefe0 100644 --- a/trunk/src/app/srs_app_gb28181.cpp +++ b/trunk/src/app/srs_app_gb28181.cpp @@ -22,6 +22,10 @@ #include #include #include +#include +#include +#include +#include #include using namespace std; @@ -420,12 +424,12 @@ srs_error_t SrsGbListener::initialize(SrsConfDirective* conf) bool sip_enabled = _srs_config->get_stream_caster_sip_enable(conf); if (!sip_enabled) { - return srs_error_new(ERROR_GB_CONFIG, "GB SIP is required"); + srs_warn("GB SIP is disabled."); + } else { + int port = _srs_config->get_stream_caster_sip_listen(conf); + sip_listener_->set_endpoint(ip, port)->set_label("SIP-TCP"); } - int port = _srs_config->get_stream_caster_sip_listen(conf); - sip_listener_->set_endpoint(ip, port)->set_label("SIP-TCP"); - return err; } @@ -441,6 +445,24 @@ srs_error_t SrsGbListener::listen() return srs_error_wrap(err, "listen"); } + if ((err = listen_api()) != srs_success) { + return srs_error_wrap(err, "listen api"); + } + + return err; +} + +srs_error_t SrsGbListener::listen_api() +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Fetch api from hybrid manager, not from SRS. + ISrsHttpServeMux* http_api_mux = _srs_hybrid->srs()->instance()->api_server(); + + if ((err = http_api_mux->handle("/gb/v1/publish/", new SrsGoApiGbPublish(conf_))) != srs_success) { + return srs_error_wrap(err, "handle publish"); + } + return err; } @@ -549,11 +571,16 @@ std::string SrsGbSipTcpConn::device_id() return register_->device_id(); } +void SrsGbSipTcpConn::set_device_id(const std::string &id) +{ + register_->from_address_user_ = id; +} + void SrsGbSipTcpConn::set_cid(const SrsContextId& cid) { if (owner_cid_) owner_cid_->set_cid(cid); - receiver_->set_cid(cid); - sender_->set_cid(cid); + if (receiver_) receiver_->set_cid(cid); + if (sender_) sender_->set_cid(cid); cid_ = cid; } @@ -2681,5 +2708,117 @@ void srs_sip_parse_address(const std::string& address, std::string& user, std::s } } +SrsGoApiGbPublish::SrsGoApiGbPublish(SrsConfDirective* conf) +{ + conf_ = conf->copy(); +} + +SrsGoApiGbPublish::~SrsGoApiGbPublish() +{ + srs_freep(conf_); +} + +srs_error_t SrsGoApiGbPublish::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) +{ + srs_error_t err = srs_success; + + SrsUniquePtr res(SrsJsonAny::object()); + + if ((err = do_serve_http(w, r, res.get())) != srs_success) { + srs_warn("GB error %s", srs_error_desc(err).c_str()); + res->set("code", SrsJsonAny::integer(srs_error_code(err))); + res->set("desc", SrsJsonAny::str(srs_error_code_str(err).c_str())); + srs_freep(err); + return srs_api_response(w, r, res->dumps()); + } + + return srs_api_response(w, r, res->dumps()); +} + +srs_error_t SrsGoApiGbPublish::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res) +{ + srs_error_t err = srs_success; + + // For each GB session, we use short-term HTTP connection. + w->header()->set("Connection", "Close"); + + // Parse req, the request json object, from body. + SrsSharedPtr req; + if (true) { + string req_json; + if ((err = r->body_read_all(req_json)) != srs_success) { + return srs_error_wrap(err, "read body"); + } + + SrsJsonAny* json = SrsJsonAny::loads(req_json); + if (!json || !json->is_object()) { + srs_freep(json); + return srs_error_new(ERROR_HTTP_DATA_INVALID, "invalid body %s", req_json.c_str()); + } + + req = SrsSharedPtr(json->to_object()); + } + + // Fetch params from req object. + SrsJsonAny* prop = NULL; + if ((prop = req->ensure_property_string("id")) == NULL) { + return srs_error_new(ERROR_HTTP_DATA_INVALID, "id required"); + } + string id = prop->to_str(); + + if ((prop = req->ensure_property_string("ssrc")) == NULL) { + return srs_error_new(ERROR_HTTP_DATA_INVALID, "ssrc required"); + } + uint64_t ssrc = atoi(prop->to_str().c_str()); + + if ((err = bind_session(id, ssrc)) != srs_success) { + return srs_error_wrap(err, "bind session"); + } + + res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); + int port = _srs_config->get_stream_caster_listen(conf_); + res->set("port", SrsJsonAny::integer(port)); + res->set("is_tcp", SrsJsonAny::boolean(true)); // only tcp supported + + srs_trace("GB publish id: %s, ssrc=%lu", id.c_str(), ssrc); + + return err; +} + +srs_error_t SrsGoApiGbPublish::bind_session(std::string id, uint64_t ssrc) +{ + srs_error_t err = srs_success; + + SrsSharedResource* session = NULL; + session = dynamic_cast*>(_srs_gb_manager->find_by_id(id)); + if (session) { + return srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "stream already exists"); + } + + session = dynamic_cast*>(_srs_gb_manager->find_by_fast_id(ssrc)); + if (session) { + return srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "ssrc already exists"); + } + + // Create new GB session. + SrsGbSession* raw_session = new SrsGbSession(); + raw_session->setup(conf_); + + session = new SrsSharedResource(raw_session); + _srs_gb_manager->add_with_id(id, session); + _srs_gb_manager->add_with_fast_id(ssrc, session); + + SrsExecutorCoroutine* executor = new SrsExecutorCoroutine(_srs_gb_manager, session, raw_session, raw_session); + raw_session->setup_owner(session, executor, executor); + raw_session->sip_transport()->set_device_id(id); + + if ((err = executor->start()) != srs_success) { + srs_freep(executor); + return srs_error_wrap(err, "gb session"); + } + + return err; +} + SrsResourceManager* _srs_gb_manager = NULL; diff --git a/trunk/src/app/srs_app_gb28181.hpp b/trunk/src/app/srs_app_gb28181.hpp index e44b618c6..be6250948 100644 --- a/trunk/src/app/srs_app_gb28181.hpp +++ b/trunk/src/app/srs_app_gb28181.hpp @@ -89,6 +89,34 @@ enum SrsGbSipState }; std::string srs_gb_sip_state(SrsGbSipState state); +// For external SIP server mode, where SRS acts only as a media relay server +// 1. SIP server POST request via HTTP API with stream ID and SSRC +// 2. SRS create session using ID and SSRC, return a port for receiving media streams (indicated in conf). +// 3. External streaming service connect to the port, and send RTP stream (with the above SSRC) +// 4. SRS forward the stream to RTMP stream, named after ID +// +// Request: +// POST /gb/v1/publish/ +// { +// "id": "...", +// "ssrc": "..." +// } +// Response: +// {"port":9000, "is_tcp": true} +class SrsGoApiGbPublish : public ISrsHttpHandler +{ +private: + SrsConfDirective* conf_; +public: + SrsGoApiGbPublish(SrsConfDirective* conf); + virtual ~SrsGoApiGbPublish(); +public: + virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +private: + virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); + srs_error_t bind_session(std::string stream, uint64_t ssrc); +}; + // The main logic object for GB, the session. // Each session contains a SIP object and a media object, that are managed by session. This means session always // lives longer than SIP and media, and session will dispose SIP and media when session disposed. In another word, @@ -191,6 +219,8 @@ public: // Interface ISrsTcpHandler public: virtual srs_error_t on_tcp_client(ISrsListener* listener, srs_netfd_t stfd); +private: + srs_error_t listen_api(); }; // A GB28181 TCP SIP connection. @@ -234,6 +264,10 @@ public: public: // Get the SIP device id. std::string device_id(); + // For use with external SIP signaling server ONLY + // When using an external SIP signaling server, device id are not available, so manual configuration is required + // This id will be used as the stream name in the RTMP protocol + void set_device_id(const std::string& id); // Set the cid of all coroutines. virtual void set_cid(const SrsContextId& cid); private: diff --git a/trunk/src/core/srs_core_version6.hpp b/trunk/src/core/srs_core_version6.hpp index 021f633e7..c6259016b 100644 --- a/trunk/src/core/srs_core_version6.hpp +++ b/trunk/src/core/srs_core_version6.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 6 #define VERSION_MINOR 0 -#define VERSION_REVISION 143 +#define VERSION_REVISION 144 #endif