Skip to content

Commit

Permalink
GB28181: Support external SIP server. v6.0.144 (#4101)
Browse files Browse the repository at this point in the history
For #3369 to support an external powerful SIP server, do not use the
embedded SIP server of SRS.
For more information, detailed steps, system architecture, and
background explanation, please see
https://ossrs.net/lts/zh-cn/docs/v6/doc/gb28181#external-sip

---------

Co-authored-by: Jacob Su <suzp1984@gmail.com>
Co-authored-by: winlin <winlinvip@gmail.com>
  • Loading branch information
3 people authored Jul 27, 2024
1 parent f76be5f commit 65ad907
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 9 deletions.
6 changes: 4 additions & 2 deletions trunk/conf/full.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
53 changes: 53 additions & 0 deletions trunk/conf/gb28181-without-sip.conf
Original file line number Diff line number Diff line change
@@ -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;
}
}

1 change: 1 addition & 0 deletions trunk/doc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v6-changes"></a>

## 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)
Expand Down
151 changes: 145 additions & 6 deletions trunk/src/app/srs_app_gb28181.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#include <srs_app_pithy_print.hpp>
#include <srs_app_rtmp_conn.hpp>
#include <srs_protocol_raw_avc.hpp>
#include <srs_app_server.hpp>
#include <srs_protocol_json.hpp>
#include <srs_app_http_api.hpp>
#include <srs_app_statistic.hpp>

#include <sstream>
using namespace std;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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<SrsJsonObject> 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<SrsJsonObject> 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<SrsJsonObject>(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<SrsGbSession>* session = NULL;
session = dynamic_cast<SrsSharedResource<SrsGbSession>*>(_srs_gb_manager->find_by_id(id));
if (session) {
return srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "stream already exists");
}

session = dynamic_cast<SrsSharedResource<SrsGbSession>*>(_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<SrsGbSession>(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;

34 changes: 34 additions & 0 deletions trunk/src/app/srs_app_gb28181.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -191,6 +219,8 @@ class SrsGbListener : public ISrsListener, public ISrsTcpHandler
// 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.
Expand Down Expand Up @@ -234,6 +264,10 @@ class SrsGbSipTcpConn : public ISrsResource, public ISrsCoroutineHandler, 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:
Expand Down
2 changes: 1 addition & 1 deletion trunk/src/core/srs_core_version6.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

#define VERSION_MAJOR 6
#define VERSION_MINOR 0
#define VERSION_REVISION 143
#define VERSION_REVISION 144

#endif

0 comments on commit 65ad907

Please sign in to comment.