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