From 3919e86cc0f47d583f0375d76099d89316ecb0c8 Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Wed, 8 Oct 2025 10:00:46 -0400 Subject: [PATCH] AI: Add utest to cover gb module. --- trunk/configure | 2 +- trunk/src/app/srs_app_caster_flv.cpp | 28 +- trunk/src/app/srs_app_caster_flv.hpp | 5 + trunk/src/app/srs_app_config.hpp | 2 + trunk/src/app/srs_app_factory.cpp | 11 + trunk/src/app/srs_app_factory.hpp | 6 + trunk/src/app/srs_app_gb28181.cpp | 175 +- trunk/src/app/srs_app_gb28181.hpp | 220 +- trunk/src/app/srs_app_listener.cpp | 33 +- trunk/src/app/srs_app_listener.hpp | 31 +- trunk/src/app/srs_app_mpegts_udp.cpp | 3 +- trunk/src/app/srs_app_rtc_conn.cpp | 12 +- trunk/src/app/srs_app_rtc_conn.hpp | 40 +- trunk/src/app/srs_app_rtc_network.cpp | 83 +- trunk/src/app/srs_app_rtc_network.hpp | 104 +- trunk/src/app/srs_app_rtc_server.cpp | 4 +- trunk/src/app/srs_app_server.cpp | 15 +- trunk/src/app/srs_app_server.hpp | 16 +- trunk/src/app/srs_app_st.hpp | 3 +- trunk/src/kernel/srs_kernel_pithy_print.cpp | 8 + trunk/src/kernel/srs_kernel_pithy_print.hpp | 15 +- trunk/src/kernel/srs_kernel_ps.cpp | 13 + trunk/src/kernel/srs_kernel_ps.hpp | 18 +- trunk/src/kernel/srs_kernel_resource.hpp | 10 + trunk/src/protocol/srs_protocol_raw_avc.cpp | 24 + trunk/src/protocol/srs_protocol_raw_avc.hpp | 57 +- trunk/src/utest/srs_utest_app10.cpp | 46 + trunk/src/utest/srs_utest_app10.hpp | 10 + trunk/src/utest/srs_utest_app13.cpp | 10 + trunk/src/utest/srs_utest_app13.hpp | 2 + trunk/src/utest/srs_utest_app14.cpp | 2298 +++++++++++++++++++ trunk/src/utest/srs_utest_app14.hpp | 458 ++++ trunk/src/utest/srs_utest_app6.cpp | 29 + trunk/src/utest/srs_utest_app6.hpp | 8 + trunk/src/utest/srs_utest_app7.cpp | 23 + trunk/src/utest/srs_utest_app7.hpp | 5 + trunk/src/utest/srs_utest_gb28181.cpp | 22 +- trunk/src/utest/srs_utest_service.cpp | 23 + trunk/src/utest/srs_utest_service.hpp | 5 + 39 files changed, 3706 insertions(+), 171 deletions(-) create mode 100644 trunk/src/utest/srs_utest_app14.cpp create mode 100644 trunk/src/utest/srs_utest_app14.hpp diff --git a/trunk/configure b/trunk/configure index 3a0c67e6f..1d56b142b 100755 --- a/trunk/configure +++ b/trunk/configure @@ -384,7 +384,7 @@ if [[ $SRS_UTEST == YES ]]; then "srs_utest_coworkers" "srs_utest_pithy_print" "srs_utest_kernel3" "srs_utest_protocol4" "srs_utest_protocol3" "srs_utest_app" "srs_utest_app2" "srs_utest_app3" "srs_utest_app4" "srs_utest_app5" "srs_utest_app6" "srs_utest_app7" "srs_utest_app8" "srs_utest_app9" - "srs_utest_app10" "srs_utest_app11" "srs_utest_app12" "srs_utest_app13") + "srs_utest_app10" "srs_utest_app11" "srs_utest_app12" "srs_utest_app13" "srs_utest_app14") # Always include SRT utest MODULE_FILES+=("srs_utest_srt") if [[ $SRS_GB28181 == YES ]]; then diff --git a/trunk/src/app/srs_app_caster_flv.cpp b/trunk/src/app/srs_app_caster_flv.cpp index f78faaedc..63b758046 100644 --- a/trunk/src/app/srs_app_caster_flv.cpp +++ b/trunk/src/app/srs_app_caster_flv.cpp @@ -47,7 +47,8 @@ srs_error_t SrsHttpFlvListener::initialize(SrsConfDirective *c) return srs_error_new(ERROR_STREAM_CASTER_PORT, "invalid port=%d", port); } - listener_->set_endpoint(srs_net_address_any(), port)->set_label("PUSH-FLV"); + listener_->set_endpoint(srs_net_address_any(), port); + listener_->set_label("PUSH-FLV"); if ((err = caster_->initialize(c)) != srs_success) { return srs_error_wrap(err, "init caster port=%d", port); @@ -160,11 +161,36 @@ void SrsAppCasterFlv::add(ISrsResource *conn, bool *exists) manager_->add(conn, exists); } +void SrsAppCasterFlv::add_with_id(const std::string &id, ISrsResource *conn) +{ + manager_->add_with_id(id, conn); +} + +void SrsAppCasterFlv::add_with_fast_id(uint64_t id, ISrsResource *conn) +{ + manager_->add_with_fast_id(id, conn); +} + ISrsResource *SrsAppCasterFlv::at(int index) { return manager_->at(index); } +ISrsResource *SrsAppCasterFlv::find_by_id(std::string id) +{ + return manager_->find_by_id(id); +} + +ISrsResource *SrsAppCasterFlv::find_by_fast_id(uint64_t id) +{ + return manager_->find_by_fast_id(id); +} + +ISrsResource *SrsAppCasterFlv::find_by_name(std::string name) +{ + return manager_->find_by_name(name); +} + void SrsAppCasterFlv::remove(ISrsResource *c) { ISrsConnection *conn = dynamic_cast(c); diff --git a/trunk/src/app/srs_app_caster_flv.hpp b/trunk/src/app/srs_app_caster_flv.hpp index 94249274f..95dcb37f7 100644 --- a/trunk/src/app/srs_app_caster_flv.hpp +++ b/trunk/src/app/srs_app_caster_flv.hpp @@ -74,7 +74,12 @@ public: virtual bool empty(); virtual size_t size(); virtual void add(ISrsResource *conn, bool *exists = NULL); + virtual void add_with_id(const std::string &id, ISrsResource *conn); + virtual void add_with_fast_id(uint64_t id, ISrsResource *conn); virtual ISrsResource *at(int index); + virtual ISrsResource *find_by_id(std::string id); + virtual ISrsResource *find_by_fast_id(uint64_t id); + virtual ISrsResource *find_by_name(std::string name); virtual void remove(ISrsResource *c); virtual void subscribe(ISrsDisposingHandler *h); virtual void unsubscribe(ISrsDisposingHandler *h); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 1500e9499..b2a265afe 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -359,6 +359,8 @@ public: virtual std::vector get_stream_casters() = 0; virtual bool get_stream_caster_enabled(SrsConfDirective *conf) = 0; virtual std::string get_stream_caster_engine(SrsConfDirective *conf) = 0; + virtual std::string get_stream_caster_output(SrsConfDirective *conf) = 0; + virtual int get_stream_caster_listen(SrsConfDirective *conf) = 0; public: // Exporter config diff --git a/trunk/src/app/srs_app_factory.cpp b/trunk/src/app/srs_app_factory.cpp index ebbca7f67..5cad78215 100644 --- a/trunk/src/app/srs_app_factory.cpp +++ b/trunk/src/app/srs_app_factory.cpp @@ -22,6 +22,7 @@ #include #include #include +#include ISrsAppFactory::ISrsAppFactory() { @@ -128,6 +129,16 @@ ISrsDvrSegmenter *SrsAppFactory::create_dvr_mp4_segmenter() return new SrsDvrMp4Segmenter(); } +ISrsGbMediaTcpConn *SrsAppFactory::create_gb_media_tcp_conn() +{ + return new SrsGbMediaTcpConn(); +} + +ISrsGbSession *SrsAppFactory::create_gb_session() +{ + return new SrsGbSession(); +} + SrsFinalFactory::SrsFinalFactory() { } diff --git a/trunk/src/app/srs_app_factory.hpp b/trunk/src/app/srs_app_factory.hpp index 95543a502..9143015d8 100644 --- a/trunk/src/app/srs_app_factory.hpp +++ b/trunk/src/app/srs_app_factory.hpp @@ -29,6 +29,8 @@ class SrsRtcTrackDescription; class ISrsFlvTransmuxer; class ISrsMp4Encoder; class ISrsDvrSegmenter; +class ISrsGbMediaTcpConn; +class ISrsGbSession; // The factory to create app objects. class ISrsAppFactory @@ -57,6 +59,8 @@ public: virtual ISrsMp4Encoder *create_mp4_encoder() = 0; virtual ISrsDvrSegmenter *create_dvr_flv_segmenter() = 0; virtual ISrsDvrSegmenter *create_dvr_mp4_segmenter() = 0; + virtual ISrsGbMediaTcpConn *create_gb_media_tcp_conn() = 0; + virtual ISrsGbSession *create_gb_session() = 0; }; // The factory to create app objects. @@ -86,6 +90,8 @@ public: virtual ISrsMp4Encoder *create_mp4_encoder(); virtual ISrsDvrSegmenter *create_dvr_flv_segmenter(); virtual ISrsDvrSegmenter *create_dvr_mp4_segmenter(); + virtual ISrsGbMediaTcpConn *create_gb_media_tcp_conn(); + virtual ISrsGbSession *create_gb_session(); }; extern ISrsAppFactory *_srs_app_factory; diff --git a/trunk/src/app/srs_app_gb28181.cpp b/trunk/src/app/srs_app_gb28181.cpp index 3bcd1f91c..d5c46488f 100644 --- a/trunk/src/app/srs_app_gb28181.cpp +++ b/trunk/src/app/srs_app_gb28181.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include using namespace std; @@ -55,6 +56,14 @@ std::string srs_gb_state(SrsGbSessionState ostate, SrsGbSessionState state) return srs_fmt_sprintf("%s->%s", srs_gb_session_state(ostate).c_str(), srs_gb_session_state(state).c_str()); } +ISrsGbSession::ISrsGbSession() +{ +} + +ISrsGbSession::~ISrsGbSession() +{ +} + SrsGbSession::SrsGbSession() : media_(new SrsGbMediaTcpConn()) { wrapper_ = NULL; @@ -86,23 +95,27 @@ SrsGbSession::SrsGbSession() : media_(new SrsGbMediaTcpConn()) cid_ = _srs_context->generate_id(); _srs_context->set_id(cid_); // Also change current coroutine cid as session's. + + config_ = _srs_config; } SrsGbSession::~SrsGbSession() { srs_freep(muxer_); srs_freep(ppp_); + + config_ = NULL; } void SrsGbSession::setup(SrsConfDirective *conf) { - std::string output = _srs_config->get_stream_caster_output(conf); + std::string output = config_->get_stream_caster_output(conf); muxer_->setup(output); srs_trace("Session: Start output=%s", output.c_str()); } -void SrsGbSession::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) +void SrsGbSession::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) { wrapper_ = wrapper; owner_coroutine_ = owner_coroutine; @@ -114,7 +127,7 @@ void SrsGbSession::on_executor_done(ISrsInterruptable *executor) owner_coroutine_ = NULL; } -void SrsGbSession::on_ps_pack(SrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs) +void SrsGbSession::on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs) { // Got a new context, that is new media transport. if (media_id_ != ctx->media_id_) { @@ -172,7 +185,7 @@ void SrsGbSession::on_ps_pack(SrsPackContext *ctx, SrsPsPacket *ps, const std::v } } -void SrsGbSession::on_media_transport(SrsSharedResource media) +void SrsGbSession::on_media_transport(SrsSharedResource media) { media_ = media; @@ -301,16 +314,34 @@ std::string SrsGbSession::desc() return "GBS"; } +ISrsGbListener::ISrsGbListener() +{ +} + +ISrsGbListener::~ISrsGbListener() +{ +} + SrsGbListener::SrsGbListener() { conf_ = NULL; media_listener_ = new SrsTcpListener(this); + + config_ = _srs_config; + api_server_owner_ = _srs_server; + gb_manager_ = _srs_gb_manager; + app_factory_ = _srs_app_factory; } SrsGbListener::~SrsGbListener() { srs_freep(conf_); srs_freep(media_listener_); + + config_ = NULL; + api_server_owner_ = NULL; + gb_manager_ = NULL; + app_factory_ = NULL; } srs_error_t SrsGbListener::initialize(SrsConfDirective *conf) @@ -322,8 +353,9 @@ srs_error_t SrsGbListener::initialize(SrsConfDirective *conf) string ip = srs_net_address_any(); if (true) { - int port = _srs_config->get_stream_caster_listen(conf); - media_listener_->set_endpoint(ip, port)->set_label("GB-TCP"); + int port = config_->get_stream_caster_listen(conf); + media_listener_->set_endpoint(ip, port); + media_listener_->set_label("GB-TCP"); } return err; @@ -348,7 +380,7 @@ srs_error_t SrsGbListener::listen_api() { srs_error_t err = srs_success; - ISrsHttpServeMux *mux = _srs_server->api_server(); + ISrsHttpServeMux *mux = api_server_owner_->api_server(); if ((err = mux->handle("/gb/v1/publish/", new SrsGoApiGbPublish(conf_))) != srs_success) { return srs_error_wrap(err, "handle publish"); } @@ -366,13 +398,13 @@ srs_error_t SrsGbListener::on_tcp_client(ISrsListener *listener, srs_netfd_t stf // Handle TCP connections. if (listener == media_listener_) { - SrsGbMediaTcpConn *raw_conn = new SrsGbMediaTcpConn(); + ISrsGbMediaTcpConn *raw_conn = app_factory_->create_gb_media_tcp_conn(); raw_conn->setup(stfd); - SrsSharedResource *conn = new SrsSharedResource(raw_conn); - _srs_gb_manager->add(conn, NULL); + SrsSharedResource *conn = new SrsSharedResource(raw_conn); + gb_manager_->add(conn, NULL); - SrsExecutorCoroutine *executor = new SrsExecutorCoroutine(_srs_gb_manager, conn, raw_conn, raw_conn); + SrsExecutorCoroutine *executor = new SrsExecutorCoroutine(gb_manager_, conn, raw_conn, raw_conn); raw_conn->setup_owner(conn, executor, executor); if ((err = executor->start()) != srs_success) { @@ -395,6 +427,14 @@ ISrsPsPackHandler::~ISrsPsPackHandler() { } +ISrsGbMediaTcpConn::ISrsGbMediaTcpConn() +{ +} + +ISrsGbMediaTcpConn::~ISrsGbMediaTcpConn() +{ +} + SrsGbMediaTcpConn::SrsGbMediaTcpConn() { pack_ = new SrsPackContext(this); @@ -409,6 +449,8 @@ SrsGbMediaTcpConn::SrsGbMediaTcpConn() session_ = NULL; connected_ = false; nn_rtcp_ = 0; + + gb_manager_ = _srs_gb_manager; } SrsGbMediaTcpConn::~SrsGbMediaTcpConn() @@ -416,6 +458,8 @@ SrsGbMediaTcpConn::~SrsGbMediaTcpConn() srs_freep(conn_); srs_freepa(buffer_); srs_freep(pack_); + + gb_manager_ = NULL; } void SrsGbMediaTcpConn::setup(srs_netfd_t stfd) @@ -424,7 +468,7 @@ void SrsGbMediaTcpConn::setup(srs_netfd_t stfd) conn_ = new SrsTcpConnection(stfd); } -void SrsGbMediaTcpConn::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) +void SrsGbMediaTcpConn::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) { wrapper_ = wrapper; owner_coroutine_ = owner_coroutine; @@ -511,7 +555,7 @@ srs_error_t SrsGbMediaTcpConn::do_cycle() SrsRecoverablePsContext context; // If bytes is not enough(defined by SRS_PS_MIN_REQUIRED), ignore. - context.ctx_.set_detect_ps_integrity(true); + context.ctx_->set_detect_ps_integrity(true); // Previous left bytes, to parse in next loop. uint32_t reserved = 0; @@ -538,8 +582,8 @@ srs_error_t SrsGbMediaTcpConn::do_cycle() } if (length > SRS_GB_LARGE_PACKET) { - const SrsPsDecodeHelper &h = context.ctx_.helper_; - srs_warn("PS: Large length=%u, previous-seq=%u, previous-ts=%u", length, h.rtp_seq_, h.rtp_ts_); + const SrsPsDecodeHelper *h = context.ctx_->helper(); + srs_warn("PS: Large length=%u, previous-seq=%u, previous-ts=%u", length, h->rtp_seq_, h->rtp_ts_); } // Read length of bytes of RTP packet. @@ -629,7 +673,7 @@ srs_error_t SrsGbMediaTcpConn::on_ps_pack(SrsPsPacket *ps, const std::vector *session = dynamic_cast *>(_srs_gb_manager->find_by_fast_id(ssrc)); + SrsSharedResource *session = dynamic_cast *>(gb_manager_->find_by_fast_id(ssrc)); if (!session) return err; - SrsGbSession *raw_session = (*session).get(); + ISrsGbSession *raw_session = (*session).get(); srs_assert(raw_session); // Notice session to use current media connection. @@ -651,6 +695,14 @@ srs_error_t SrsGbMediaTcpConn::bind_session(uint32_t ssrc, SrsGbSession **psessi return err; } +ISrsMpegpsQueue::ISrsMpegpsQueue() +{ +} + +ISrsMpegpsQueue::~ISrsMpegpsQueue() +{ +} + SrsMpegpsQueue::SrsMpegpsQueue() { nb_audios_ = nb_videos_ = 0; @@ -725,7 +777,15 @@ SrsMediaPacket *SrsMpegpsQueue::dequeue() return NULL; } -SrsGbMuxer::SrsGbMuxer(SrsGbSession *session) +ISrsGbMuxer::ISrsGbMuxer() +{ +} + +ISrsGbMuxer::~ISrsGbMuxer() +{ +} + +SrsGbMuxer::SrsGbMuxer(ISrsGbSession *session) { sdk_ = NULL; session_ = session; @@ -743,6 +803,8 @@ SrsGbMuxer::SrsGbMuxer(SrsGbSession *session) queue_ = new SrsMpegpsQueue(); pprint_ = SrsPithyPrint::create_caster(); + + app_factory_ = _srs_app_factory; } SrsGbMuxer::~SrsGbMuxer() @@ -754,6 +816,8 @@ SrsGbMuxer::~SrsGbMuxer() srs_freep(aac_); srs_freep(queue_); srs_freep(pprint_); + + app_factory_ = NULL; } void SrsGbMuxer::setup(std::string output) @@ -1272,7 +1336,7 @@ srs_error_t SrsGbMuxer::connect() srs_utime_t cto = SRS_CONSTS_RTMP_TIMEOUT; srs_utime_t sto = SRS_CONSTS_RTMP_PULSE; - sdk_ = new SrsSimpleRtmpClient(url, cto, sto); + sdk_ = app_factory_->create_rtmp_client(url, cto, sto); if ((err = sdk_->connect()) != srs_success) { close(); @@ -1300,7 +1364,7 @@ void SrsGbMuxer::close() h264_pps_ = ""; } -SrsPackContext::SrsPackContext(ISrsPsPackHandler *handler) +ISrsPackContext::ISrsPackContext() { static uint32_t gid = 0; media_id_ = ++gid; @@ -1309,7 +1373,14 @@ SrsPackContext::SrsPackContext(ISrsPsPackHandler *handler) media_nn_recovered_ = 0; media_nn_msgs_dropped_ = 0; media_reserved_ = 0; +} +ISrsPackContext::~ISrsPackContext() +{ +} + +SrsPackContext::SrsPackContext(ISrsPsPackHandler *handler) +{ ps_ = new SrsPsPacket(NULL); handler_ = handler; } @@ -1391,13 +1462,23 @@ void SrsPackContext::on_recover_mode(int nn_recover) } } +ISrsRecoverablePsContext::ISrsRecoverablePsContext() +{ +} + +ISrsRecoverablePsContext::~ISrsRecoverablePsContext() +{ +} + SrsRecoverablePsContext::SrsRecoverablePsContext() { recover_ = 0; + ctx_ = new SrsPsContext(); } SrsRecoverablePsContext::~SrsRecoverablePsContext() { + srs_freep(ctx_); } srs_error_t SrsRecoverablePsContext::decode_rtp(SrsBuffer *stream, int reserved, ISrsPsMessageHandler *handler) @@ -1434,9 +1515,9 @@ srs_error_t SrsRecoverablePsContext::decode_rtp(SrsBuffer *stream, int reserved, SrsBuffer b((char *)rtp_raw->payload_, rtp_raw->nn_payload_); // srs_trace("GB: Got RTP length=%d, payload=%d, seq=%u, ts=%d", length, rtp_raw->nn_payload, rtp.header_.get_sequence(), rtp.header_.get_timestamp()); - ctx_.helper_.rtp_seq_ = rtp.header_.get_sequence(); - ctx_.helper_.rtp_ts_ = rtp.header_.get_timestamp(); - ctx_.helper_.rtp_pt_ = rtp.header_.get_payload_type(); + ctx_->helper()->rtp_seq_ = rtp.header_.get_sequence(); + ctx_->helper()->rtp_ts_ = rtp.header_.get_timestamp(); + ctx_->helper()->rtp_pt_ = rtp.header_.get_payload_type(); if ((err = decode(&b, handler)) != srs_success) { return srs_error_wrap(err, "decode"); } @@ -1466,7 +1547,7 @@ srs_error_t SrsRecoverablePsContext::decode(SrsBuffer *stream, ISrsPsMessageHand } // Got packet to decode. - if ((err = ctx_.decode(stream, handler)) != srs_success) { + if ((err = ctx_->decode(stream, handler)) != srs_success) { return enter_recover_mode(stream, handler, stream->pos(), srs_error_wrap(err, "decode pack")); } return err; @@ -1482,13 +1563,13 @@ srs_error_t SrsRecoverablePsContext::enter_recover_mode(SrsBuffer *stream, ISrsP stream->skip(pos - stream->pos()); string bytes = srs_strings_dumps_hex(stream->head(), stream->left(), 8); - SrsPsDecodeHelper &h = ctx_.helper_; - uint16_t pack_seq = h.pack_first_seq_; - uint16_t pack_msgs = h.pack_nn_msgs_; - uint16_t lsopm = h.pack_pre_msg_last_seq_; - SrsTsMessage *last = ctx_.last(); + SrsPsDecodeHelper *h = ctx_->helper(); + uint16_t pack_seq = h->pack_first_seq_; + uint16_t pack_msgs = h->pack_nn_msgs_; + uint16_t lsopm = h->pack_pre_msg_last_seq_; + SrsTsMessage *last = ctx_->last(); srs_warn("PS: Enter recover=%d, seq=%u, ts=%u, pt=%u, pack=%u, msgs=%u, lsopm=%u, last=%u/%u, bytes=[%s], pos=%d, left=%d for err %s", - recover_, h.rtp_seq_, h.rtp_ts_, h.rtp_pt_, pack_seq, pack_msgs, lsopm, last->PES_packet_length_, last->payload_->length(), + recover_, h->rtp_seq_, h->rtp_ts_, h->rtp_pt_, pack_seq, pack_msgs, lsopm, last->PES_packet_length_, last->payload_->length(), bytes.c_str(), npos, stream->left(), srs_error_desc(err).c_str()); // If RTP packet exceed SRS_GB_LARGE_PACKET, which is large packet, might be correct length and impossible to @@ -1500,11 +1581,11 @@ srs_error_t SrsRecoverablePsContext::enter_recover_mode(SrsBuffer *stream, ISrsP // Sometimes, we're unable to recover it, so we limit the max retry. if (recover_ > SRS_GB_MAX_RECOVER) { return srs_error_wrap(err, "exceed max recover, pack=%u, pack-seq=%u, seq=%u", - h.pack_id_, h.pack_first_seq_, h.rtp_seq_); + h->pack_id_, h->pack_first_seq_, h->rtp_seq_); } // Reap and dispose last incomplete message. - SrsTsMessage *msg = ctx_.reap(); + SrsTsMessage *msg = ctx_->reap(); srs_freep(msg); // Skip all left bytes in buffer, reset error because recovered. stream->skip(stream->left()); @@ -1519,7 +1600,7 @@ srs_error_t SrsRecoverablePsContext::enter_recover_mode(SrsBuffer *stream, ISrsP void SrsRecoverablePsContext::quit_recover_mode(SrsBuffer *stream, ISrsPsMessageHandler *handler) { string bytes = srs_strings_dumps_hex(stream->head(), stream->left(), 8); - srs_warn("PS: Quit recover=%d, seq=%u, bytes=[%s], pos=%d, left=%d", recover_, ctx_.helper_.rtp_seq_, + srs_warn("PS: Quit recover=%d, seq=%u, bytes=[%s], pos=%d, left=%d", recover_, ctx_->helper()->rtp_seq_, bytes.c_str(), stream->pos(), stream->left()); recover_ = 0; } @@ -1550,11 +1631,19 @@ bool srs_skip_util_pack(SrsBuffer *stream) SrsGoApiGbPublish::SrsGoApiGbPublish(SrsConfDirective *conf) { conf_ = conf->copy(); + + config_ = _srs_config; + gb_manager_ = _srs_gb_manager; + app_factory_ = _srs_app_factory; } SrsGoApiGbPublish::~SrsGoApiGbPublish() { srs_freep(conf_); + + config_ = NULL; + gb_manager_ = NULL; + app_factory_ = NULL; } srs_error_t SrsGoApiGbPublish::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) @@ -1615,7 +1704,7 @@ srs_error_t SrsGoApiGbPublish::do_serve_http(ISrsHttpResponseWriter *w, ISrsHttp } res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - int port = _srs_config->get_stream_caster_listen(conf_); + int port = config_->get_stream_caster_listen(conf_); res->set("port", SrsJsonAny::integer(port)); res->set("is_tcp", SrsJsonAny::boolean(true)); // only tcp supported @@ -1628,26 +1717,26 @@ 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)); + SrsSharedResource *session = NULL; + session = dynamic_cast *>(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)); + session = dynamic_cast *>(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(); + ISrsGbSession *raw_session = app_factory_->create_gb_session(); 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); + session = new SrsSharedResource(raw_session); + gb_manager_->add_with_id(id, session); + gb_manager_->add_with_fast_id(ssrc, session); - SrsExecutorCoroutine *executor = new SrsExecutorCoroutine(_srs_gb_manager, session, raw_session, raw_session); + SrsExecutorCoroutine *executor = new SrsExecutorCoroutine(gb_manager_, session, raw_session, raw_session); raw_session->setup_owner(session, executor, executor); raw_session->device_id_ = id; diff --git a/trunk/src/app/srs_app_gb28181.hpp b/trunk/src/app/srs_app_gb28181.hpp index b44cd2661..39bbb5a7e 100644 --- a/trunk/src/app/srs_app_gb28181.hpp +++ b/trunk/src/app/srs_app_gb28181.hpp @@ -24,11 +24,8 @@ class SrsTcpConnection; class ISrsCoroutine; class SrsPackContext; class SrsBuffer; - class SrsGbSession; - class SrsGbMediaTcpConn; - class SrsAlonePithyPrint; class SrsGbMuxer; class SrsSimpleRtmpClient; @@ -39,6 +36,27 @@ class SrsMediaPacket; class SrsPithyPrint; class SrsRawAacStream; class ISrsHttpServeMux; +class ISrsGbMuxer; +class ISrsGbSession; +class ISrsPackContext; +class ISrsMpegpsQueue; +class ISrsPsContext; +class ISrsListener; +class ISrsProtocolReadWriter; +class ISrsRawH264Stream; +class ISrsRawHEVCStream; +class ISrsRawAacStream; +class ISrsPithyPrint; +class ISrsBasicRtmpClient; +class SrsTsMessage; +class SrsPsPacket; +class ISrsGbSession; +class ISrsGbMediaTcpConn; +class ISrsAppConfig; +class ISrsApiServerOwner; +class ISrsResourceManager; +class ISrsAppFactory; +class ISrsIpListener; // The state machine for GB session. // init: @@ -55,6 +73,7 @@ enum SrsGbSessionState { SrsGbSessionStateEstablished, }; std::string srs_gb_session_state(SrsGbSessionState state); +std::string srs_gb_state(SrsGbSessionState ostate, SrsGbSessionState 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 @@ -72,6 +91,11 @@ std::string srs_gb_session_state(SrsGbSessionState state); // {"port":9000, "is_tcp": true} class SrsGoApiGbPublish : public ISrsHttpHandler { +private: + ISrsAppConfig *config_; + ISrsResourceManager *gb_manager_; + ISrsAppFactory *app_factory_; + private: SrsConfDirective *conf_; @@ -87,18 +111,42 @@ private: srs_error_t bind_session(std::string stream, uint64_t ssrc); }; +// The interface for GB session. +class ISrsGbSession : public ISrsResource, public ISrsCoroutineHandler, public ISrsExecutorHandler +{ +public: + std::string device_id_; + +public: + ISrsGbSession(); + virtual ~ISrsGbSession(); + +public: + // Initialize the GB session. + virtual void setup(SrsConfDirective *conf) = 0; + // Setup the owner, the wrapper is the shared ptr, the interruptable object is the coroutine, and the cid is the context id. + virtual void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) = 0; + // Notice session to use current media connection. + virtual void on_media_transport(SrsSharedResource media) = 0; +public: + virtual void on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs) = 0; +}; + // The main logic object for GB, the session. // Each session contains a media object, that are managed by session. This means session always // lives longer than media, and session will dispose media when session disposed. In another word, // media objects use directly pointer to session, while session use shared ptr. -class SrsGbSession : public ISrsResource, public ISrsCoroutineHandler, public ISrsExecutorHandler +class SrsGbSession : public ISrsGbSession { +private: + ISrsAppConfig *config_; + private: SrsContextId cid_; private: // The shared resource which own this object, we should never free it because it's managed by shared ptr. - SrsSharedResource *wrapper_; + SrsSharedResource *wrapper_; // The owner coroutine, allow user to interrupt the loop. ISrsInterruptable *owner_coroutine_; ISrsContextIdSetter *owner_cid_; @@ -106,8 +154,8 @@ private: private: SrsGbSessionState state_; - SrsSharedResource media_; - SrsGbMuxer *muxer_; + SrsSharedResource media_; + ISrsGbMuxer *muxer_; private: // When wait for media connecting, timeout if exceed. @@ -148,17 +196,17 @@ public: // Initialize the GB session. void setup(SrsConfDirective *conf); // Setup the owner, the wrapper is the shared ptr, the interruptable object is the coroutine, and the cid is the context id. - void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); + void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); // Interface ISrsExecutorHandler public: virtual void on_executor_done(ISrsInterruptable *executor); public: // When got a pack of messages. - void on_ps_pack(SrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs); + void on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs); // When got available media transport. - void on_media_transport(SrsSharedResource media); + void on_media_transport(SrsSharedResource media); // Interface ISrsCoroutineHandler public: @@ -176,12 +224,28 @@ public: virtual std::string desc(); }; +// The listener for GB. +class ISrsGbListener : public ISrsListener +{ +public: + ISrsGbListener(); + virtual ~ISrsGbListener(); + +public: +}; + // The Media listener for GB. -class SrsGbListener : public ISrsListener, public ISrsTcpHandler +class SrsGbListener : public ISrsGbListener, public ISrsTcpHandler { +private: + ISrsAppConfig *config_; + ISrsApiServerOwner *api_server_owner_; + ISrsResourceManager *gb_manager_; + ISrsAppFactory *app_factory_; + private: SrsConfDirective *conf_; - SrsTcpListener *media_listener_; + ISrsIpListener *media_listener_; public: SrsGbListener(); @@ -212,26 +276,50 @@ public: virtual srs_error_t on_ps_pack(SrsPsPacket *ps, const std::vector &msgs) = 0; }; -// A GB28181 TCP media connection, for PS stream. -class SrsGbMediaTcpConn : public ISrsResource, public ISrsCoroutineHandler, public ISrsPsPackHandler, public ISrsExecutorHandler +// The interface for GB media transport. +class ISrsGbMediaTcpConn : public ISrsResource, public ISrsCoroutineHandler, public ISrsExecutorHandler { +public: + ISrsGbMediaTcpConn(); + virtual ~ISrsGbMediaTcpConn(); + +public: + // Setup object, to keep empty constructor. + virtual void setup(srs_netfd_t stfd) = 0; + // Setup the owner, the wrapper is the shared ptr, the interruptable object is the coroutine, and the cid is the context id. + virtual void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) = 0; + // Whether media is connected. + virtual bool is_connected() = 0; + // Interrupt transport by session. + virtual void interrupt() = 0; + // Set the cid of all coroutines. + virtual void set_cid(const SrsContextId &cid) = 0; +}; + +// A GB28181 TCP media connection, for PS stream. +class SrsGbMediaTcpConn : public ISrsGbMediaTcpConn, // It's a resource, coroutine handler, and executor handler. + public ISrsPsPackHandler +{ +private: + ISrsResourceManager *gb_manager_; + private: bool connected_; // The owner session object, note that we use the raw pointer and should never free it. - SrsGbSession *session_; + ISrsGbSession *session_; uint32_t nn_rtcp_; private: // The shared resource which own this object, we should never free it because it's managed by shared ptr. - SrsSharedResource *wrapper_; + SrsSharedResource *wrapper_; // The owner coroutine, allow user to interrupt the loop. ISrsInterruptable *owner_coroutine_; ISrsContextIdSetter *owner_cid_; SrsContextId cid_; private: - SrsPackContext *pack_; - SrsTcpConnection *conn_; + ISrsPackContext *pack_; + ISrsProtocolReadWriter *conn_; uint8_t *buffer_; public: @@ -242,7 +330,7 @@ public: // Setup object, to keep empty constructor. void setup(srs_netfd_t stfd); // Setup the owner, the wrapper is the shared ptr, the interruptable object is the coroutine, and the cid is the context id. - void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); + void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); // Interface ISrsExecutorHandler public: virtual void on_executor_done(ISrsInterruptable *executor); @@ -270,13 +358,25 @@ public: private: // Create session if no one, or bind to an existed session. - srs_error_t bind_session(uint32_t ssrc, SrsGbSession **psession); + srs_error_t bind_session(uint32_t ssrc, ISrsGbSession **psession); +}; + +// The interface for mpegps queue. +class ISrsMpegpsQueue +{ +public: + ISrsMpegpsQueue(); + virtual ~ISrsMpegpsQueue(); + +public: + virtual srs_error_t push(SrsMediaPacket *msg) = 0; + virtual SrsMediaPacket *dequeue() = 0; }; // The queue for mpegts over udp to send packets. // For the aac in mpegts contains many flv packets in a pes packet, // we must recalc the timestamp. -class SrsMpegpsQueue +class SrsMpegpsQueue : public ISrsMpegpsQueue { private: // The key: dts, value: msg. @@ -293,24 +393,39 @@ public: virtual SrsMediaPacket *dequeue(); }; +// The interface for GB muxer. +class ISrsGbMuxer +{ +public: + ISrsGbMuxer(); + virtual ~ISrsGbMuxer(); + +public: + virtual void setup(std::string output) = 0; + virtual srs_error_t on_ts_message(SrsTsMessage *msg) = 0; +}; + // Mux GB28181 to RTMP. -class SrsGbMuxer +class SrsGbMuxer : public ISrsGbMuxer { private: - // The owner session object, note that we use the raw pointer and should never free it. - SrsGbSession *session_; - std::string output_; - SrsSimpleRtmpClient *sdk_; + ISrsAppFactory *app_factory_; private: - SrsRawH264Stream *avc_; + // The owner session object, note that we use the raw pointer and should never free it. + ISrsGbSession *session_; + std::string output_; + ISrsBasicRtmpClient *sdk_; + +private: + ISrsRawH264Stream *avc_; std::string h264_sps_; bool h264_sps_changed_; std::string h264_pps_; bool h264_pps_changed_; bool h264_sps_pps_sent_; - SrsRawHEVCStream *hevc_; + ISrsRawHEVCStream *hevc_; bool vps_sps_pps_change_; std::string h265_vps_; std::string h265_sps_; @@ -318,15 +433,15 @@ private: bool vps_sps_pps_sent_; private: - SrsRawAacStream *aac_; + ISrsRawAacStream *aac_; std::string aac_specific_config_; private: - SrsMpegpsQueue *queue_; - SrsPithyPrint *pprint_; + ISrsMpegpsQueue *queue_; + ISrsPithyPrint *pprint_; public: - SrsGbMuxer(SrsGbSession *session); + SrsGbMuxer(ISrsGbSession *session); virtual ~SrsGbMuxer(); public: @@ -352,11 +467,21 @@ private: virtual void close(); }; -// Recoverable PS context for GB28181. -class SrsRecoverablePsContext +// The interface for recoverable PS context. +class ISrsRecoverablePsContext { public: - SrsPsContext ctx_; + ISrsRecoverablePsContext(); + virtual ~ISrsRecoverablePsContext(); + +public: +}; + +// Recoverable PS context for GB28181. +class SrsRecoverablePsContext : public ISrsRecoverablePsContext +{ +public: + ISrsPsContext *ctx_; private: // If decoding error, enter the recover mode. Drop all left bytes util next pack header. @@ -380,13 +505,8 @@ private: void quit_recover_mode(SrsBuffer *stream, ISrsPsMessageHandler *handler); }; -// The PS pack context, for GB28181 to process based on PS pack, which contains a video and audios messages. For large -// video frame, it might be split to multiple PES packets, which must be group to one video frame. -// Please note that a pack might contain multiple audio frames, so size of audio PES packet should not exceed 64KB, -// which is limited by the 16 bits PES_packet_length. -// We also correct the timestamp, or DTS/PTS of video frames, which might be 0 if more than one video PES packets in a -// PS pack stream. -class SrsPackContext : public ISrsPsMessageHandler +// The interface for PS pack context. +class ISrsPackContext : public ISrsPsMessageHandler { public: // Each media transport only use one context, so the context id is the media id. @@ -396,6 +516,19 @@ public: uint64_t media_nn_msgs_dropped_; uint64_t media_reserved_; +public: + ISrsPackContext(); + virtual ~ISrsPackContext(); +}; + +// The PS pack context, for GB28181 to process based on PS pack, which contains a video and audios messages. For large +// video frame, it might be split to multiple PES packets, which must be group to one video frame. +// Please note that a pack might contain multiple audio frames, so size of audio PES packet should not exceed 64KB, +// which is limited by the 16 bits PES_packet_length. +// We also correct the timestamp, or DTS/PTS of video frames, which might be 0 if more than one video PES packets in a +// PS pack stream. +class SrsPackContext : public ISrsPackContext +{ private: // To process a pack of TS/PS messages. ISrsPsPackHandler *handler_; @@ -408,8 +541,9 @@ public: SrsPackContext(ISrsPsPackHandler *handler); virtual ~SrsPackContext(); -private: +public: void clear(); + // Interface ISrsPsMessageHandler public: virtual srs_error_t on_ts_message(SrsTsMessage *msg); diff --git a/trunk/src/app/srs_app_listener.cpp b/trunk/src/app/srs_app_listener.cpp index a68cd2db2..6a691c343 100644 --- a/trunk/src/app/srs_app_listener.cpp +++ b/trunk/src/app/srs_app_listener.cpp @@ -64,6 +64,14 @@ ISrsListener::~ISrsListener() { } +ISrsIpListener::ISrsIpListener() +{ +} + +ISrsIpListener::~ISrsIpListener() +{ +} + ISrsTcpHandler::ISrsTcpHandler() { } @@ -92,13 +100,13 @@ SrsUdpListener::~SrsUdpListener() srs_freepa(buf_); } -SrsUdpListener *SrsUdpListener::set_label(const std::string &label) +ISrsListener *SrsUdpListener::set_label(const std::string &label) { label_ = label; return this; } -SrsUdpListener *SrsUdpListener::set_endpoint(const std::string &i, int p) +ISrsListener *SrsUdpListener::set_endpoint(const std::string &i, int p) { ip_ = i; port_ = p; @@ -250,20 +258,20 @@ SrsTcpListener::~SrsTcpListener() srs_close_stfd(lfd_); } -SrsTcpListener *SrsTcpListener::set_label(const std::string &label) +ISrsListener *SrsTcpListener::set_label(const std::string &label) { label_ = label; return this; } -SrsTcpListener *SrsTcpListener::set_endpoint(const std::string &i, int p) +ISrsListener *SrsTcpListener::set_endpoint(const std::string &i, int p) { ip_ = i; port_ = p; return this; } -SrsTcpListener *SrsTcpListener::set_endpoint(const std::string &endpoint) +ISrsListener *SrsTcpListener::set_endpoint(const std::string &endpoint) { std::string ip; int port_; @@ -358,7 +366,7 @@ SrsMultipleTcpListeners::~SrsMultipleTcpListeners() } } -SrsMultipleTcpListeners *SrsMultipleTcpListeners::set_label(const std::string &label) +ISrsListener *SrsMultipleTcpListeners::set_label(const std::string &label) { for (vector::iterator it = listeners_.begin(); it != listeners_.end(); ++it) { SrsTcpListener *l = *it; @@ -368,6 +376,16 @@ SrsMultipleTcpListeners *SrsMultipleTcpListeners::set_label(const std::string &l return this; } +ISrsListener *SrsMultipleTcpListeners::set_endpoint(const std::string &i, int p) +{ + for (vector::iterator it = listeners_.begin(); it != listeners_.end(); ++it) { + SrsTcpListener *l = *it; + l->set_endpoint(i, p); + } + + return this; +} + SrsMultipleTcpListeners *SrsMultipleTcpListeners::add(const std::vector &endpoints) { for (int i = 0; i < (int)endpoints.size(); i++) { @@ -376,7 +394,8 @@ SrsMultipleTcpListeners *SrsMultipleTcpListeners::add(const std::vectorset_endpoint(ip, port)); + l->set_endpoint(ip, port); + listeners_.push_back(l); } return this; diff --git a/trunk/src/app/srs_app_listener.hpp b/trunk/src/app/srs_app_listener.hpp index d9e291e8c..c0b9a2d93 100644 --- a/trunk/src/app/srs_app_listener.hpp +++ b/trunk/src/app/srs_app_listener.hpp @@ -64,6 +64,18 @@ public: virtual srs_error_t listen() = 0; }; +// The IP layer TCP/UDP listener. +class ISrsIpListener : public ISrsListener +{ +public: + ISrsIpListener(); + virtual ~ISrsIpListener(); + +public: + virtual ISrsListener *set_endpoint(const std::string &i, int p) = 0; + virtual ISrsListener *set_label(const std::string &label) = 0; +}; + // The tcp connection handler. class ISrsTcpHandler { @@ -77,7 +89,7 @@ public: }; // Bind udp port, start thread to recv packet and handler it. -class SrsUdpListener : public ISrsCoroutineHandler +class SrsUdpListener : public ISrsCoroutineHandler, public ISrsIpListener { protected: std::string label_; @@ -98,8 +110,8 @@ public: virtual ~SrsUdpListener(); public: - SrsUdpListener *set_label(const std::string &label); - SrsUdpListener *set_endpoint(const std::string &i, int p); + ISrsListener *set_label(const std::string &label); + ISrsListener *set_endpoint(const std::string &i, int p); private: virtual int fd(); @@ -120,7 +132,7 @@ private: }; // Bind and listen tcp port, use handler to process the client. -class SrsTcpListener : public ISrsCoroutineHandler, public ISrsListener +class SrsTcpListener : public ISrsCoroutineHandler, public ISrsIpListener { private: std::string label_; @@ -137,9 +149,9 @@ public: virtual ~SrsTcpListener(); public: - SrsTcpListener *set_label(const std::string &label); - SrsTcpListener *set_endpoint(const std::string &i, int p); - SrsTcpListener *set_endpoint(const std::string &endpoint); + ISrsListener *set_label(const std::string &label); + ISrsListener *set_endpoint(const std::string &i, int p); + ISrsListener *set_endpoint(const std::string &endpoint); int port(); public: @@ -154,7 +166,7 @@ private: }; // Bind and listen tcp port, use handler to process the client. -class SrsMultipleTcpListeners : public ISrsListener, public ISrsTcpHandler +class SrsMultipleTcpListeners : public ISrsIpListener, public ISrsTcpHandler { private: ISrsTcpHandler *handler_; @@ -165,7 +177,8 @@ public: virtual ~SrsMultipleTcpListeners(); public: - SrsMultipleTcpListeners *set_label(const std::string &label); + ISrsListener *set_label(const std::string &label); + ISrsListener *set_endpoint(const std::string &i, int p); SrsMultipleTcpListeners *add(const std::vector &endpoints); public: diff --git a/trunk/src/app/srs_app_mpegts_udp.cpp b/trunk/src/app/srs_app_mpegts_udp.cpp index 37ae80a21..5a9e17c0c 100644 --- a/trunk/src/app/srs_app_mpegts_udp.cpp +++ b/trunk/src/app/srs_app_mpegts_udp.cpp @@ -52,7 +52,8 @@ srs_error_t SrsUdpCasterListener::initialize(SrsConfDirective *conf) return srs_error_new(ERROR_STREAM_CASTER_PORT, "invalid port=%d", port); } - listener_->set_endpoint(srs_net_address_any(), port)->set_label("MPEGTS"); + listener_->set_endpoint(srs_net_address_any(), port); + listener_->set_label("MPEGTS"); if ((err = caster_->initialize(conf)) != srs_success) { return srs_error_wrap(err, "init caster port=%d", port); diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index 7da3d0964..c60c7fd7c 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -1821,6 +1821,14 @@ void SrsRtcPublishStream::update_send_report_time(uint32_t ssrc, const SrsNtp &n } } +ISrsRtcConnection::ISrsRtcConnection() +{ +} + +ISrsRtcConnection::~ISrsRtcConnection() +{ +} + ISrsRtcConnectionNackTimerHandler::ISrsRtcConnectionNackTimerHandler() { } @@ -2411,12 +2419,12 @@ void SrsRtcConnection::alive() last_stun_time_ = srs_time_now_cached(); } -SrsRtcUdpNetwork *SrsRtcConnection::udp() +ISrsRtcNetwork *SrsRtcConnection::udp() { return networks_->udp(); } -SrsRtcTcpNetwork *SrsRtcConnection::tcp() +ISrsRtcNetwork *SrsRtcConnection::tcp() { return networks_->tcp(); } diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp index f9e8f9b6b..6ac85e90d 100644 --- a/trunk/src/app/srs_app_rtc_conn.hpp +++ b/trunk/src/app/srs_app_rtc_conn.hpp @@ -552,16 +552,42 @@ public: virtual srs_error_t exec_rtc_async_work(ISrsAsyncCallTask *t) = 0; }; -// A RTC Peer Connection, SDP level object. -// -// For performance, we use non-public from resource, -// see https://stackoverflow.com/questions/3747066/c-cannot-convert-from-base-a-to-derived-type-b-via-virtual-base-a -class SrsRtcConnection : public ISrsResource, // It's a resource. +// The interface for RTC connection. +class ISrsRtcConnection : public ISrsResource, // It's a resource. public ISrsDisposingHandler, public ISrsExpire, public ISrsRtcPacketSender, public ISrsRtcPacketReceiver, public ISrsRtcConnectionNackTimerHandler +{ +public: + ISrsRtcConnection(); + virtual ~ISrsRtcConnection(); + +public: + // DTLS callbacks. + virtual srs_error_t on_dtls_handshake_done() = 0; + virtual srs_error_t on_dtls_alert(std::string type, std::string desc) = 0; + // RTP/RTCP packet handling. + virtual srs_error_t on_rtp_cipher(char *data, int nb_data) = 0; + virtual srs_error_t on_rtp_plaintext(char *data, int nb_data) = 0; + virtual srs_error_t on_rtcp(char *data, int nb_data) = 0; + // STUN binding request. + virtual srs_error_t on_binding_request(SrsStunPacket *r, std::string &ice_pwd) = 0; + // Network access. + virtual ISrsRtcNetwork *udp() = 0; + virtual ISrsRtcNetwork *tcp() = 0; + // Keep alive. + virtual void alive() = 0; + // Context switching. + virtual void switch_to_context() = 0; +}; + +// A RTC Peer Connection, SDP level object. +// +// For performance, we use non-public from resource, +// see https://stackoverflow.com/questions/3747066/c-cannot-convert-from-base-a-to-derived-type-b-via-virtual-base-a +class SrsRtcConnection : public ISrsRtcConnection { friend class SrsSecurityTransport; @@ -698,8 +724,8 @@ public: void alive(); public: - SrsRtcUdpNetwork *udp(); - SrsRtcTcpNetwork *tcp(); + ISrsRtcNetwork *udp(); + ISrsRtcNetwork *tcp(); public: // send rtcp diff --git a/trunk/src/app/srs_app_rtc_network.cpp b/trunk/src/app/srs_app_rtc_network.cpp index 9aa499180..afd1a398d 100644 --- a/trunk/src/app/srs_app_rtc_network.cpp +++ b/trunk/src/app/srs_app_rtc_network.cpp @@ -36,7 +36,15 @@ extern bool srs_is_dtls(const uint8_t *data, size_t len); extern bool srs_is_rtp_or_rtcp(const uint8_t *data, size_t len); extern bool srs_is_rtcp(const uint8_t *data, size_t len); -SrsRtcNetworks::SrsRtcNetworks(SrsRtcConnection *conn) +ISrsRtcNetworks::ISrsRtcNetworks() +{ +} + +ISrsRtcNetworks::~ISrsRtcNetworks() +{ +} + +SrsRtcNetworks::SrsRtcNetworks(ISrsRtcConnection *conn) { conn_ = conn; delta_ = new SrsEphemeralDelta(); @@ -74,12 +82,12 @@ void SrsRtcNetworks::set_state(SrsRtcNetworkState state) tcp_->set_state(state); } -SrsRtcUdpNetwork *SrsRtcNetworks::udp() +ISrsRtcNetwork *SrsRtcNetworks::udp() { return udp_; } -SrsRtcTcpNetwork *SrsRtcNetworks::tcp() +ISrsRtcNetwork *SrsRtcNetworks::tcp() { return tcp_; } @@ -117,6 +125,15 @@ SrsRtcDummyNetwork::~SrsRtcDummyNetwork() { } +srs_error_t SrsRtcDummyNetwork::initialize(SrsSessionConfig *cfg, bool dtls, bool srtp) +{ + return srs_success; +} + +void SrsRtcDummyNetwork::set_state(SrsRtcNetworkState state) +{ +} + bool SrsRtcDummyNetwork::is_establelished() { return true; @@ -132,6 +149,11 @@ srs_error_t SrsRtcDummyNetwork::on_dtls_alert(std::string type, std::string desc return srs_success; } +srs_error_t SrsRtcDummyNetwork::on_dtls(char *data, int nb_data) +{ + return srs_success; +} + srs_error_t SrsRtcDummyNetwork::protect_rtp(void *packet, int *nb_cipher) { return srs_success; @@ -142,12 +164,27 @@ srs_error_t SrsRtcDummyNetwork::protect_rtcp(void *packet, int *nb_cipher) return srs_success; } +srs_error_t SrsRtcDummyNetwork::on_stun(SrsStunPacket *r, char *data, int nb_data) +{ + return srs_success; +} + +srs_error_t SrsRtcDummyNetwork::on_rtp(char *data, int nb_data) +{ + return srs_success; +} + +srs_error_t SrsRtcDummyNetwork::on_rtcp(char *data, int nb_data) +{ + return srs_success; +} + srs_error_t SrsRtcDummyNetwork::write(void *buf, size_t size, ssize_t *nwrite) { return srs_success; } -SrsRtcUdpNetwork::SrsRtcUdpNetwork(SrsRtcConnection *conn, SrsEphemeralDelta *delta) +SrsRtcUdpNetwork::SrsRtcUdpNetwork(ISrsRtcConnection *conn, ISrsEphemeralDelta *delta) { state_ = SrsRtcNetworkStateInit; conn_ = conn; @@ -155,6 +192,8 @@ SrsRtcUdpNetwork::SrsRtcUdpNetwork(SrsRtcConnection *conn, SrsEphemeralDelta *de sendonly_skt_ = NULL; pp_address_change_ = new SrsErrorPithyPrint(); transport_ = new SrsSecurityTransport(this); + + conn_manager_ = _srs_conn_manager; } SrsRtcUdpNetwork::~SrsRtcUdpNetwork() @@ -171,6 +210,8 @@ SrsRtcUdpNetwork::~SrsRtcUdpNetwork() } srs_freep(pp_address_change_); + + conn_manager_ = NULL; } srs_error_t SrsRtcUdpNetwork::initialize(SrsSessionConfig *cfg, bool dtls, bool srtp) @@ -363,11 +404,11 @@ void SrsRtcUdpNetwork::update_sendonly_socket(SrsUdpMuxSocket *skt) // If no cache, build cache and setup the relations in connection. if (!addr_cache) { peer_addresses_[peer_id] = addr_cache = skt->copy_sendonly(); - _srs_conn_manager->add_with_id(peer_id, conn_); + conn_manager_->add_with_id(peer_id, conn_); uint64_t fast_id = skt->fast_id(); if (fast_id) { - _srs_conn_manager->add_with_fast_id(fast_id, conn_); + conn_manager_->add_with_fast_id(fast_id, conn_); } } @@ -451,7 +492,7 @@ srs_error_t SrsRtcUdpNetwork::write(void *buf, size_t size, ssize_t *nwrite) return sendonly_skt_->sendto(buf, size, SRS_UTIME_NO_TIMEOUT); } -SrsRtcTcpNetwork::SrsRtcTcpNetwork(SrsRtcConnection *conn, SrsEphemeralDelta *delta) : owner_(new SrsRtcTcpConn()) +SrsRtcTcpNetwork::SrsRtcTcpNetwork(ISrsRtcConnection *conn, ISrsEphemeralDelta *delta) : owner_(new SrsRtcTcpConn()) { conn_ = conn; delta_ = delta; @@ -718,6 +759,17 @@ void SrsRtcTcpConn::setup() pkt_ = NULL; delta_ = NULL; skt_ = NULL; + + conn_manager_ = _srs_conn_manager; + stat_ = _srs_stat; +} + +ISrsRtcTcpConn::ISrsRtcTcpConn() +{ +} + +ISrsRtcTcpConn::~ISrsRtcTcpConn() +{ } SrsRtcTcpConn::SrsRtcTcpConn() @@ -743,9 +795,12 @@ SrsRtcTcpConn::~SrsRtcTcpConn() srs_freepa(pkt_); srs_freep(delta_); srs_freep(skt_); + + conn_manager_ = NULL; + stat_ = NULL; } -void SrsRtcTcpConn::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) +void SrsRtcTcpConn::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) { wrapper_ = wrapper; owner_coroutine_ = owner_coroutine; @@ -789,8 +844,8 @@ srs_error_t SrsRtcTcpConn::cycle() srs_error_t err = do_cycle(); // Only stat the HTTP streaming clients, ignore all API clients. - _srs_stat->on_disconnect(get_id().c_str(), err); - _srs_stat->kbps_add_delta(get_id().c_str(), delta_); + stat_->on_disconnect(get_id().c_str(), err); + stat_->kbps_add_delta(get_id().c_str(), delta_); // Only remove session when network is established, because client might use other UDP network. if (session_ && session_->tcp()->is_establelished()) { @@ -888,7 +943,7 @@ srs_error_t SrsRtcTcpConn::handshake() } srs_assert(!session_); - SrsRtcConnection *session = dynamic_cast(_srs_conn_manager->find_by_name(ping.get_username())); + ISrsRtcConnection *session = dynamic_cast(conn_manager_->find_by_name(ping.get_username())); // TODO: FIXME: For ICE trickle, we may get STUN packets before SDP answer, so maybe should response it. if (!session) { return srs_error_new(ERROR_RTC_TCP_STUN, "no session, stun username=%s", ping.get_username().c_str()); @@ -910,12 +965,12 @@ srs_error_t SrsRtcTcpConn::handshake() // For each binding request, update the TCP socket. if (ping.is_binding_request()) { - session_->tcp()->update_sendonly_socket(skt_); - session_->tcp()->set_peer_id(ip_, port_); + network->update_sendonly_socket(skt_); + network->set_peer_id(ip_, port_); } // Use the session network to handle packet. - return session_->tcp()->on_stun(&ping, pkt_, npkt); + return network->on_stun(&ping, pkt_, npkt); } srs_error_t SrsRtcTcpConn::read_packet(char *pkt, int *nb_pkt) diff --git a/trunk/src/app/srs_app_rtc_network.hpp b/trunk/src/app/srs_app_rtc_network.hpp index 1e018af3a..8ca83e0dd 100644 --- a/trunk/src/app/srs_app_rtc_network.hpp +++ b/trunk/src/app/srs_app_rtc_network.hpp @@ -20,18 +20,23 @@ class ISrsResourceManager; class ISrsCoroutine; class SrsNetworkDelta; +class ISrsNetworkDelta; class SrsTcpConnection; +class ISrsTcpConnection; class ISrsKbpsDelta; class SrsUdpMuxSocket; class SrsErrorPithyPrint; class ISrsRtcTransport; class SrsEphemeralDelta; +class ISrsEphemeralDelta; class ISrsKbpsDelta; class SrsRtcUdpNetwork; +class ISrsRtcUdpNetwork; class ISrsRtcNetwork; class SrsRtcTcpNetwork; class SrsRtcDummyNetwork; class SrsRtcTcpConn; +class ISrsRtcTcpConn; // The network stat. enum SrsRtcNetworkState { @@ -43,25 +48,35 @@ enum SrsRtcNetworkState { SrsRtcNetworkStateClosed = 5, }; +// The network manager interface. +class ISrsRtcNetworks +{ +public: + ISrsRtcNetworks(); + virtual ~ISrsRtcNetworks(); + +public: +}; + // A group of networks, each has its own DTLS and SRTP context. -class SrsRtcNetworks +class SrsRtcNetworks : public ISrsRtcNetworks { private: // Network over UDP. - SrsRtcUdpNetwork *udp_; + ISrsRtcNetwork *udp_; // Network over TCP - SrsRtcTcpNetwork *tcp_; + ISrsRtcNetwork *tcp_; // Network over dummy - SrsRtcDummyNetwork *dummy_; + ISrsRtcNetwork *dummy_; private: // WebRTC session object. - SrsRtcConnection *conn_; + ISrsRtcConnection *conn_; // Delta object for statistics. - SrsEphemeralDelta *delta_; + ISrsEphemeralDelta *delta_; public: - SrsRtcNetworks(SrsRtcConnection *conn); + SrsRtcNetworks(ISrsRtcConnection *conn); virtual ~SrsRtcNetworks(); // DTLS transport functions. public: @@ -71,8 +86,8 @@ public: // Connection level state machine, for ARQ of UDP packets. void set_state(SrsRtcNetworkState state); // Get the UDP network object. - SrsRtcUdpNetwork *udp(); - SrsRtcTcpNetwork *tcp(); + ISrsRtcNetwork *udp(); + ISrsRtcNetwork *tcp(); // Get an available network. ISrsRtcNetwork *available(); @@ -88,11 +103,19 @@ public: ISrsRtcNetwork(); virtual ~ISrsRtcNetwork(); +public: + // Initialize the network with DTLS and SRTP configuration. + virtual srs_error_t initialize(SrsSessionConfig *cfg, bool dtls, bool srtp) = 0; + // Set the network state. + virtual void set_state(SrsRtcNetworkState state) = 0; + public: // Callback when DTLS connected. virtual srs_error_t on_dtls_handshake_done() = 0; // Callback when DTLS disconnected. virtual srs_error_t on_dtls_alert(std::string type, std::string desc) = 0; + // Handle DTLS data. + virtual srs_error_t on_dtls(char *data, int nb_data) = 0; public: // Protect RTP packet by SRTP context. @@ -100,6 +123,14 @@ public: // Protect RTCP packet by SRTP context. virtual srs_error_t protect_rtcp(void *packet, int *nb_cipher) = 0; +public: + // Handle STUN packet. + virtual srs_error_t on_stun(SrsStunPacket *r, char *data, int nb_data) = 0; + // Handle RTP packet. + virtual srs_error_t on_rtp(char *data, int nb_data) = 0; + // Handle RTCP packet. + virtual srs_error_t on_rtcp(char *data, int nb_data) = 0; + public: virtual bool is_establelished() = 0; }; @@ -113,12 +144,22 @@ public: // The interface of ISrsRtcNetwork public: + virtual srs_error_t initialize(SrsSessionConfig *cfg, bool dtls, bool srtp); + virtual void set_state(SrsRtcNetworkState state); virtual srs_error_t on_dtls_handshake_done(); virtual srs_error_t on_dtls_alert(std::string type, std::string desc); + virtual srs_error_t on_dtls(char *data, int nb_data); public: virtual srs_error_t protect_rtp(void *packet, int *nb_cipher); virtual srs_error_t protect_rtcp(void *packet, int *nb_cipher); + +public: + virtual srs_error_t on_stun(SrsStunPacket *r, char *data, int nb_data); + virtual srs_error_t on_rtp(char *data, int nb_data); + virtual srs_error_t on_rtcp(char *data, int nb_data); + +public: virtual bool is_establelished(); // Interface ISrsStreamWriter. public: @@ -128,11 +169,14 @@ public: // The WebRTC over UDP network. class SrsRtcUdpNetwork : public ISrsRtcNetwork { +private: + ISrsResourceManager *conn_manager_; + private: // WebRTC session object. - SrsRtcConnection *conn_; + ISrsRtcConnection *conn_; // Delta object for statistics. - SrsEphemeralDelta *delta_; + ISrsEphemeralDelta *delta_; SrsRtcNetworkState state_; private: @@ -146,7 +190,7 @@ private: ISrsRtcTransport *transport_; public: - SrsRtcUdpNetwork(SrsRtcConnection *conn, SrsEphemeralDelta *delta); + SrsRtcUdpNetwork(ISrsRtcConnection *conn, ISrsEphemeralDelta *delta); virtual ~SrsRtcUdpNetwork(); public: @@ -185,14 +229,14 @@ public: class SrsRtcTcpNetwork : public ISrsRtcNetwork { private: - SrsRtcConnection *conn_; - SrsEphemeralDelta *delta_; + ISrsRtcConnection *conn_; + ISrsEphemeralDelta *delta_; ISrsProtocolReadWriter *sendonly_skt_; private: // The DTLS transport over this network. ISrsRtcTransport *transport_; - SrsSharedResource owner_; + SrsSharedResource owner_; private: std::string peer_ip_; @@ -200,12 +244,12 @@ private: SrsRtcNetworkState state_; public: - SrsRtcTcpNetwork(SrsRtcConnection *conn, SrsEphemeralDelta *delta); + SrsRtcTcpNetwork(ISrsRtcConnection *conn, ISrsEphemeralDelta *delta); virtual ~SrsRtcTcpNetwork(); public: - void set_owner(SrsSharedResource v) { owner_ = v; } - SrsSharedResource owner() { return owner_; } + void set_owner(SrsSharedResource v) { owner_ = v; } + SrsSharedResource owner() { return owner_; } void update_sendonly_socket(ISrsProtocolReadWriter *skt); // ISrsRtcNetwork public: @@ -248,12 +292,28 @@ public: void dispose(); }; +// The interface for TCP connection. +class ISrsRtcTcpConn : public ISrsConnection, public ISrsCoroutineHandler, public ISrsExecutorHandler +{ +public: + ISrsRtcTcpConn(); + virtual ~ISrsRtcTcpConn(); + +public: + // Interrupt the TCP connection. + virtual void interrupt() = 0; +}; + // For WebRTC over TCP. -class SrsRtcTcpConn : public ISrsConnection, public ISrsCoroutineHandler, public ISrsExecutorHandler +class SrsRtcTcpConn : public ISrsRtcTcpConn { +private: + ISrsResourceManager *conn_manager_; + ISrsStatistic *stat_; + private: // Because session references to this object, so we should directly use the session ptr. - SrsRtcConnection *session_; + ISrsRtcConnection *session_; private: // The ip and port of client. @@ -267,7 +327,7 @@ private: private: // The shared resource which own this object, we should never free it because it's managed by shared ptr. - SrsSharedResource *wrapper_; + SrsSharedResource *wrapper_; // The owner coroutine, allow user to interrupt the loop. ISrsInterruptable *owner_coroutine_; ISrsContextIdSetter *owner_cid_; @@ -283,7 +343,7 @@ public: public: // Setup the owner, the wrapper is the shared ptr, the interruptable object is the coroutine, and the cid is the context id. - void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); + void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); public: ISrsKbpsDelta *delta(); diff --git a/trunk/src/app/srs_app_rtc_server.cpp b/trunk/src/app/srs_app_rtc_server.cpp index 527148091..52166944a 100644 --- a/trunk/src/app/srs_app_rtc_server.cpp +++ b/trunk/src/app/srs_app_rtc_server.cpp @@ -609,7 +609,9 @@ srs_error_t SrsRtcSessionManager::on_udp_packet(SrsUdpMuxSocket *skt) // For each binding request, update the UDP socket. if (ping.is_binding_request()) { - session->udp()->update_sendonly_socket(skt); + SrsRtcUdpNetwork *udp_network = dynamic_cast(session->udp()); + srs_assert(udp_network); + udp_network->update_sendonly_socket(skt); } return session->udp()->on_stun(&ping, data, size); diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index 8adbe97dc..36ce8290e 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -150,6 +150,14 @@ ISrsSignalHandler::~ISrsSignalHandler() { } +ISrsApiServerOwner::ISrsApiServerOwner() +{ +} + +ISrsApiServerOwner::~ISrsApiServerOwner() +{ +} + SrsServer::SrsServer() { signal_reload_ = false; @@ -270,7 +278,9 @@ SrsServer::~SrsServer() circuit_breaker_ = NULL; srt_sources_ = NULL; rtc_sources_ = NULL; +#ifdef SRS_RTSP rtsp_sources_ = NULL; +#endif #ifdef SRS_GB28181 gb_manager_ = NULL; #endif @@ -682,7 +692,8 @@ srs_error_t SrsServer::listen() // Create exporter server listener. if (config_->get_exporter_enabled()) { - exporter_listener_->set_endpoint(config_->get_exporter_listen())->set_label("Exporter-Server"); + exporter_listener_->set_endpoint(config_->get_exporter_listen()); + exporter_listener_->set_label("Exporter-Server"); if ((err = exporter_listener_->listen()) != srs_success) { return srs_error_wrap(err, "exporter server listen"); } @@ -1558,7 +1569,7 @@ srs_error_t SrsServer::do_on_tcp_client(ISrsListener *listener, srs_netfd_t &stf // For RTC TCP connection, use resource executor to manage the resource. SrsRtcTcpConn *raw_conn = dynamic_cast(resource); if (raw_conn) { - SrsSharedResource *conn = new SrsSharedResource(raw_conn); + SrsSharedResource *conn = new SrsSharedResource(raw_conn); SrsExecutorCoroutine *executor = new SrsExecutorCoroutine(conn_manager_, conn, raw_conn, raw_conn); raw_conn->setup_owner(conn, executor, executor); if ((err = executor->start()) != srs_success) { diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 02ce171f3..dd08bca51 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -84,6 +84,17 @@ public: virtual void on_signal(int signo) = 0; }; +// The API server owner interface. +class ISrsApiServerOwner +{ +public: + ISrsApiServerOwner(); + virtual ~ISrsApiServerOwner(); + +public: + virtual ISrsHttpServeMux *api_server() = 0; +}; + // SrsServer is the main server class of SRS (Simple Realtime Server) that provides comprehensive // streaming media server functionality. It serves as the central orchestrator for all streaming // protocols and services in a single-threaded, coroutine-based architecture. @@ -93,7 +104,8 @@ class SrsServer : public ISrsReloadHandler, // Reload framework for permormance public ISrsHourGlassHandler, public ISrsSrtClientHandler, public ISrsUdpMuxHandler, - public ISrsSignalHandler + public ISrsSignalHandler, + public ISrsApiServerOwner { private: ISrsAppConfig *config_; @@ -104,7 +116,9 @@ private: ISrsCircuitBreaker *circuit_breaker_; ISrsSrtSourceManager *srt_sources_; ISrsRtcSourceManager *rtc_sources_; +#ifdef SRS_RTSP ISrsRtspSourceManager *rtsp_sources_; +#endif #ifdef SRS_GB28181 ISrsResourceManager *gb_manager_; #endif diff --git a/trunk/src/app/srs_app_st.hpp b/trunk/src/app/srs_app_st.hpp index 858ee6979..51d7ada7d 100644 --- a/trunk/src/app/srs_app_st.hpp +++ b/trunk/src/app/srs_app_st.hpp @@ -196,7 +196,8 @@ public: // srs_freep(executor); // return err; // } -class SrsExecutorCoroutine : public ISrsResource, public ISrsStartable, public ISrsInterruptable, public ISrsContextIdSetter, public ISrsContextIdGetter, public ISrsCoroutineHandler +class SrsExecutorCoroutine : public ISrsResource, // It's a resource. + public ISrsStartable, public ISrsInterruptable, public ISrsContextIdSetter, public ISrsContextIdGetter, public ISrsCoroutineHandler { private: ISrsResourceManager *manager_; diff --git a/trunk/src/kernel/srs_kernel_pithy_print.cpp b/trunk/src/kernel/srs_kernel_pithy_print.cpp index fcedba495..dd3603c7e 100644 --- a/trunk/src/kernel/srs_kernel_pithy_print.cpp +++ b/trunk/src/kernel/srs_kernel_pithy_print.cpp @@ -171,6 +171,14 @@ bool SrsAlonePithyPrint::can_print() // The global stage manager for pithy print, multiple stages. SrsStageManager *_srs_stages = NULL; +ISrsPithyPrint::ISrsPithyPrint() +{ +} + +ISrsPithyPrint::~ISrsPithyPrint() +{ +} + SrsPithyPrint::SrsPithyPrint(int _stage_id) { stage_id_ = _stage_id; diff --git a/trunk/src/kernel/srs_kernel_pithy_print.hpp b/trunk/src/kernel/srs_kernel_pithy_print.hpp index 2fca09055..bde101b7e 100644 --- a/trunk/src/kernel/srs_kernel_pithy_print.hpp +++ b/trunk/src/kernel/srs_kernel_pithy_print.hpp @@ -95,6 +95,19 @@ public: virtual bool can_print(); }; +// The interface for pithy print. +class ISrsPithyPrint +{ +public: + ISrsPithyPrint(); + virtual ~ISrsPithyPrint(); + +public: + virtual void elapse() = 0; + virtual bool can_print() = 0; + virtual srs_utime_t age() = 0; +}; + // The stage is used for a collection of object to do print, // the print time in a stage is constant and not changed, // that is, we always got one message to print every specified time. @@ -112,7 +125,7 @@ public: // } // // read and write RTMP messages. // } -class SrsPithyPrint +class SrsPithyPrint : public ISrsPithyPrint { private: int client_id_; diff --git a/trunk/src/kernel/srs_kernel_ps.cpp b/trunk/src/kernel/srs_kernel_ps.cpp index 8c5b820c9..1199e2a02 100644 --- a/trunk/src/kernel/srs_kernel_ps.cpp +++ b/trunk/src/kernel/srs_kernel_ps.cpp @@ -43,6 +43,14 @@ ISrsPsMessageHandler::~ISrsPsMessageHandler() { } +ISrsPsContext::ISrsPsContext() +{ +} + +ISrsPsContext::~ISrsPsContext() +{ +} + SrsPsContext::SrsPsContext() { last_ = NULL; @@ -81,6 +89,11 @@ SrsTsMessage *SrsPsContext::reap() return msg; } +SrsPsDecodeHelper* SrsPsContext::helper() +{ + return &helper_; +} + srs_error_t SrsPsContext::decode(SrsBuffer *stream, ISrsPsMessageHandler *handler) { srs_error_t err = srs_success; diff --git a/trunk/src/kernel/srs_kernel_ps.hpp b/trunk/src/kernel/srs_kernel_ps.hpp index cbddaa012..4fa19ab19 100644 --- a/trunk/src/kernel/srs_kernel_ps.hpp +++ b/trunk/src/kernel/srs_kernel_ps.hpp @@ -57,8 +57,23 @@ public: virtual void on_recover_mode(int nn_recover) = 0; }; +// The interface for PS context. +class ISrsPsContext +{ +public: + ISrsPsContext(); + virtual ~ISrsPsContext(); + +public: + virtual SrsPsDecodeHelper* helper() = 0; + virtual void set_detect_ps_integrity(bool v) = 0; + virtual srs_error_t decode(SrsBuffer *stream, ISrsPsMessageHandler *handler) = 0; + virtual SrsTsMessage *last() = 0; + virtual SrsTsMessage *reap() = 0; +}; + // The PS context, to process PS PES stream. -class SrsPsContext +class SrsPsContext : public ISrsPsContext { public: SrsPsDecodeHelper helper_; @@ -87,6 +102,7 @@ public: SrsTsMessage *last(); // Reap the last message and create a fresh one. SrsTsMessage *reap(); + virtual SrsPsDecodeHelper* helper(); public: // Feed with ts packets, decode as ts message, callback handler if got one ts message. diff --git a/trunk/src/kernel/srs_kernel_resource.hpp b/trunk/src/kernel/srs_kernel_resource.hpp index 28058fe44..dc29b3359 100644 --- a/trunk/src/kernel/srs_kernel_resource.hpp +++ b/trunk/src/kernel/srs_kernel_resource.hpp @@ -93,8 +93,18 @@ public: public: // Add a resource to the manager. virtual void add(ISrsResource *conn, bool *exists = NULL) = 0; + // Add a resource with string id to the manager. + virtual void add_with_id(const std::string &id, ISrsResource *conn) = 0; + // Add a resource with fast(int) id to the manager. + virtual void add_with_fast_id(uint64_t id, ISrsResource *conn) = 0; // Get resource at specified index. virtual ISrsResource *at(int index) = 0; + // Find resource by string id. + virtual ISrsResource *find_by_id(std::string id) = 0; + // Find resource by fast(int) id. + virtual ISrsResource *find_by_fast_id(uint64_t id) = 0; + // Find resource by name. + virtual ISrsResource *find_by_name(std::string name) = 0; public: // Remove then free the specified connection. Note that the manager always free c resource, diff --git a/trunk/src/protocol/srs_protocol_raw_avc.cpp b/trunk/src/protocol/srs_protocol_raw_avc.cpp index be3558a54..5d141385b 100644 --- a/trunk/src/protocol/srs_protocol_raw_avc.cpp +++ b/trunk/src/protocol/srs_protocol_raw_avc.cpp @@ -39,6 +39,14 @@ bool srs_aac_startswith_adts(SrsBuffer *stream) return true; } +ISrsRawH264Stream::ISrsRawH264Stream() +{ +} + +ISrsRawH264Stream::~ISrsRawH264Stream() +{ +} + SrsRawH264Stream::SrsRawH264Stream() { } @@ -284,6 +292,14 @@ srs_error_t SrsRawH264Stream::mux_avc2flv(string video, int8_t frame_type, int8_ return err; } +ISrsRawHEVCStream::ISrsRawHEVCStream() +{ +} + +ISrsRawHEVCStream::~ISrsRawHEVCStream() +{ +} + SrsRawHEVCStream::SrsRawHEVCStream() { } @@ -641,6 +657,14 @@ srs_error_t SrsRawHEVCStream::mux_hevc2flv_enhanced(std::string video, int8_t fr return err; } +ISrsRawAacStream::ISrsRawAacStream() +{ +} + +ISrsRawAacStream::~ISrsRawAacStream() +{ +} + SrsRawAacStream::SrsRawAacStream() { } diff --git a/trunk/src/protocol/srs_protocol_raw_avc.hpp b/trunk/src/protocol/srs_protocol_raw_avc.hpp index d12c792dc..66262a7bf 100644 --- a/trunk/src/protocol/srs_protocol_raw_avc.hpp +++ b/trunk/src/protocol/srs_protocol_raw_avc.hpp @@ -15,8 +15,26 @@ class SrsBuffer; +// The interface for raw h.264 stream. +class ISrsRawH264Stream +{ +public: + ISrsRawH264Stream(); + virtual ~ISrsRawH264Stream(); + +public: + virtual srs_error_t annexb_demux(SrsBuffer *stream, char **pframe, int *pnb_frame) = 0; + virtual bool is_sps(char *frame, int nb_frame) = 0; + virtual bool is_pps(char *frame, int nb_frame) = 0; + virtual srs_error_t sps_demux(char *frame, int nb_frame, std::string &sps) = 0; + virtual srs_error_t pps_demux(char *frame, int nb_frame, std::string &pps) = 0; + virtual srs_error_t mux_sequence_header(std::string sps, std::string pps, std::string &sh) = 0; + virtual srs_error_t mux_ipb_frame(char *frame, int nb_frame, std::string &ibp) = 0; + virtual srs_error_t mux_avc2flv(std::string video, int8_t frame_type, int8_t avc_packet_type, uint32_t dts, uint32_t pts, char **flv, int *nb_flv) = 0; +}; + // The raw h.264 stream, in annexb. -class SrsRawH264Stream +class SrsRawH264Stream : public ISrsRawH264Stream { public: SrsRawH264Stream(); @@ -55,8 +73,28 @@ public: virtual srs_error_t mux_avc2flv(std::string video, int8_t frame_type, int8_t avc_packet_type, uint32_t dts, uint32_t pts, char **flv, int *nb_flv); }; +// The interface for raw h.265 stream. +class ISrsRawHEVCStream +{ +public: + ISrsRawHEVCStream(); + virtual ~ISrsRawHEVCStream(); + +public: + virtual srs_error_t annexb_demux(SrsBuffer *stream, char **pframe, int *pnb_frame) = 0; + virtual bool is_sps(char *frame, int nb_frame) = 0; + virtual bool is_pps(char *frame, int nb_frame) = 0; + virtual bool is_vps(char *frame, int nb_frame) = 0; + virtual srs_error_t sps_demux(char *frame, int nb_frame, std::string &sps) = 0; + virtual srs_error_t pps_demux(char *frame, int nb_frame, std::string &pps) = 0; + virtual srs_error_t vps_demux(char *frame, int nb_frame, std::string &vps) = 0; + virtual srs_error_t mux_sequence_header(std::string vps, std::string sps, std::vector &pps, std::string &sh) = 0; + virtual srs_error_t mux_ipb_frame(char *frame, int nb_frame, std::string &ibp) = 0; + virtual srs_error_t mux_hevc2flv(std::string video, int8_t frame_type, int8_t avc_packet_type, uint32_t dts, uint32_t pts, char **flv, int *nb_flv) = 0; +}; + // The raw h.265 stream, in annexb. -class SrsRawHEVCStream +class SrsRawHEVCStream : public ISrsRawHEVCStream { public: SrsRawHEVCStream(); @@ -131,8 +169,21 @@ struct SrsRawAacStreamCodec { int8_t aac_packet_type_; }; +// The interface for raw aac stream. +class ISrsRawAacStream +{ +public: + ISrsRawAacStream(); + virtual ~ISrsRawAacStream(); + +public: + virtual srs_error_t adts_demux(SrsBuffer *stream, char **pframe, int *pnb_frame, SrsRawAacStreamCodec &codec) = 0; + virtual srs_error_t mux_sequence_header(SrsRawAacStreamCodec *codec, std::string &sh) = 0; + virtual srs_error_t mux_aac2flv(char *frame, int nb_frame, SrsRawAacStreamCodec *codec, uint32_t dts, char **flv, int *nb_flv) = 0; +}; + // The raw aac stream, in adts. -class SrsRawAacStream +class SrsRawAacStream : public ISrsRawAacStream { public: SrsRawAacStream(); diff --git a/trunk/src/utest/srs_utest_app10.cpp b/trunk/src/utest/srs_utest_app10.cpp index 1d3c260fd..ddb2c1000 100644 --- a/trunk/src/utest/srs_utest_app10.cpp +++ b/trunk/src/utest/srs_utest_app10.cpp @@ -699,6 +699,14 @@ void MockConnectionManagerForResampleKbps::add(ISrsResource *conn, bool *exists) connections_.push_back(conn); } +void MockConnectionManagerForResampleKbps::add_with_id(const std::string & /*id*/, ISrsResource * /*conn*/) +{ +} + +void MockConnectionManagerForResampleKbps::add_with_fast_id(uint64_t /*id*/, ISrsResource * /*conn*/) +{ +} + ISrsResource *MockConnectionManagerForResampleKbps::at(int index) { if (index < 0 || index >= (int)connections_.size()) { @@ -707,6 +715,21 @@ ISrsResource *MockConnectionManagerForResampleKbps::at(int index) return connections_[index]; } +ISrsResource *MockConnectionManagerForResampleKbps::find_by_id(std::string /*id*/) +{ + return NULL; +} + +ISrsResource *MockConnectionManagerForResampleKbps::find_by_fast_id(uint64_t /*id*/) +{ + return NULL; +} + +ISrsResource *MockConnectionManagerForResampleKbps::find_by_name(std::string /*name*/) +{ + return NULL; +} + void MockConnectionManagerForResampleKbps::remove(ISrsResource *c) { } @@ -966,11 +989,34 @@ void MockConnectionManagerForConnectionLimit::add(ISrsResource *conn, bool *exis { } +void MockConnectionManagerForConnectionLimit::add_with_id(const std::string & /*id*/, ISrsResource * /*conn*/) +{ +} + +void MockConnectionManagerForConnectionLimit::add_with_fast_id(uint64_t /*id*/, ISrsResource * /*conn*/) +{ +} + ISrsResource *MockConnectionManagerForConnectionLimit::at(int index) { return NULL; } +ISrsResource *MockConnectionManagerForConnectionLimit::find_by_id(std::string /*id*/) +{ + return NULL; +} + +ISrsResource *MockConnectionManagerForConnectionLimit::find_by_fast_id(uint64_t /*id*/) +{ + return NULL; +} + +ISrsResource *MockConnectionManagerForConnectionLimit::find_by_name(std::string /*name*/) +{ + return NULL; +} + void MockConnectionManagerForConnectionLimit::remove(ISrsResource *c) { } diff --git a/trunk/src/utest/srs_utest_app10.hpp b/trunk/src/utest/srs_utest_app10.hpp index 9de1719a9..b896bd024 100644 --- a/trunk/src/utest/srs_utest_app10.hpp +++ b/trunk/src/utest/srs_utest_app10.hpp @@ -231,7 +231,12 @@ public: virtual bool empty(); virtual size_t size(); virtual void add(ISrsResource *conn, bool *exists = NULL); + virtual void add_with_id(const std::string &id, ISrsResource *conn); + virtual void add_with_fast_id(uint64_t id, ISrsResource *conn); virtual ISrsResource *at(int index); + virtual ISrsResource *find_by_id(std::string id); + virtual ISrsResource *find_by_fast_id(uint64_t id); + virtual ISrsResource *find_by_name(std::string name); virtual void remove(ISrsResource *c); virtual void subscribe(ISrsDisposingHandler *h); virtual void unsubscribe(ISrsDisposingHandler *h); @@ -300,7 +305,12 @@ public: virtual bool empty(); virtual size_t size(); virtual void add(ISrsResource *conn, bool *exists = NULL); + virtual void add_with_id(const std::string &id, ISrsResource *conn); + virtual void add_with_fast_id(uint64_t id, ISrsResource *conn); virtual ISrsResource *at(int index); + virtual ISrsResource *find_by_id(std::string id); + virtual ISrsResource *find_by_fast_id(uint64_t id); + virtual ISrsResource *find_by_name(std::string name); virtual void remove(ISrsResource *c); virtual void subscribe(ISrsDisposingHandler *h); virtual void unsubscribe(ISrsDisposingHandler *h); diff --git a/trunk/src/utest/srs_utest_app13.cpp b/trunk/src/utest/srs_utest_app13.cpp index c9d93b730..8d61d7585 100644 --- a/trunk/src/utest/srs_utest_app13.cpp +++ b/trunk/src/utest/srs_utest_app13.cpp @@ -3209,6 +3209,16 @@ ISrsDvrSegmenter *MockDvrAppFactory::create_dvr_mp4_segmenter() return segmenter; } +ISrsGbMediaTcpConn *MockDvrAppFactory::create_gb_media_tcp_conn() +{ + return NULL; +} + +ISrsGbSession *MockDvrAppFactory::create_gb_session() +{ + return NULL; +} + VOID TEST(DvrSegmenterTest, OpenTypicalScenario) { srs_error_t err; diff --git a/trunk/src/utest/srs_utest_app13.hpp b/trunk/src/utest/srs_utest_app13.hpp index 08a1c1950..c8b6a32e5 100644 --- a/trunk/src/utest/srs_utest_app13.hpp +++ b/trunk/src/utest/srs_utest_app13.hpp @@ -635,6 +635,8 @@ public: virtual ISrsMp4Encoder *create_mp4_encoder(); virtual ISrsDvrSegmenter *create_dvr_flv_segmenter(); virtual ISrsDvrSegmenter *create_dvr_mp4_segmenter(); + virtual ISrsGbMediaTcpConn *create_gb_media_tcp_conn(); + virtual ISrsGbSession *create_gb_session(); }; // Mock ISrsDvrSegmenter for testing SrsDvrPlan diff --git a/trunk/src/utest/srs_utest_app14.cpp b/trunk/src/utest/srs_utest_app14.cpp new file mode 100644 index 000000000..453aad4d2 --- /dev/null +++ b/trunk/src/utest/srs_utest_app14.cpp @@ -0,0 +1,2298 @@ +// +// 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 + +// Mock ISrsGbMuxer implementation +MockGbMuxer::MockGbMuxer() +{ + setup_called_ = false; + setup_output_ = ""; + on_ts_message_called_ = false; + on_ts_message_error_ = srs_success; +} + +MockGbMuxer::~MockGbMuxer() +{ +} + +void MockGbMuxer::setup(std::string output) +{ + setup_called_ = true; + setup_output_ = output; +} + +srs_error_t MockGbMuxer::on_ts_message(SrsTsMessage *msg) +{ + on_ts_message_called_ = true; + return srs_error_copy(on_ts_message_error_); +} + +void MockGbMuxer::reset() +{ + setup_called_ = false; + setup_output_ = ""; + on_ts_message_called_ = false; + srs_freep(on_ts_message_error_); +} + +// Mock ISrsAppConfig implementation +MockAppConfigForGbSession::MockAppConfigForGbSession() +{ + stream_caster_output_ = ""; +} + +MockAppConfigForGbSession::~MockAppConfigForGbSession() +{ +} + +std::string MockAppConfigForGbSession::get_stream_caster_output(SrsConfDirective *conf) +{ + return stream_caster_output_; +} + +void MockAppConfigForGbSession::set_stream_caster_output(const std::string &output) +{ + stream_caster_output_ = output; +} + +// Test GB28181 session state conversion functions +VOID TEST(GB28181Test, SessionStateConversion) +{ + // Test srs_gb_session_state() for all valid states + EXPECT_STREQ("Init", srs_gb_session_state(SrsGbSessionStateInit).c_str()); + EXPECT_STREQ("Connecting", srs_gb_session_state(SrsGbSessionStateConnecting).c_str()); + EXPECT_STREQ("Established", srs_gb_session_state(SrsGbSessionStateEstablished).c_str()); + EXPECT_STREQ("Invalid", srs_gb_session_state((SrsGbSessionState)999).c_str()); + + // Test srs_gb_state() for state transitions + EXPECT_STREQ("Init->Connecting", srs_gb_state(SrsGbSessionStateInit, SrsGbSessionStateConnecting).c_str()); + EXPECT_STREQ("Connecting->Established", srs_gb_state(SrsGbSessionStateConnecting, SrsGbSessionStateEstablished).c_str()); + EXPECT_STREQ("Established->Init", srs_gb_state(SrsGbSessionStateEstablished, SrsGbSessionStateInit).c_str()); +} + +// Test SrsGbSession setup and setup_owner methods +VOID TEST(GB28181Test, SessionSetupAndOwner) +{ + // Create mock dependencies + SrsUniquePtr mock_config(new MockAppConfigForGbSession()); + MockGbMuxer *mock_muxer = new MockGbMuxer(); + + // Create session + SrsUniquePtr session(new SrsGbSession()); + + // Inject mock dependencies + session->config_ = mock_config.get(); + session->muxer_ = mock_muxer; + + // Setup mock config to return test output + mock_config->set_stream_caster_output("rtmp://127.0.0.1/live/test_stream"); + + // Test setup() method + SrsConfDirective *conf = NULL; + session->setup(conf); + + // Verify muxer->setup() was called with correct output + EXPECT_TRUE(mock_muxer->setup_called_); + EXPECT_STREQ("rtmp://127.0.0.1/live/test_stream", mock_muxer->setup_output_.c_str()); + + // Test setup_owner() method + SrsSharedResource *wrapper = NULL; + ISrsInterruptable *owner_coroutine = (ISrsInterruptable *)0x1234; + ISrsContextIdSetter *owner_cid = (ISrsContextIdSetter *)0x5678; + + session->setup_owner(wrapper, owner_coroutine, owner_cid); + + // Verify owner fields are set correctly + EXPECT_EQ(wrapper, session->wrapper_); + EXPECT_EQ(owner_coroutine, session->owner_coroutine_); + EXPECT_EQ(owner_cid, session->owner_cid_); + + // Test on_executor_done() method + session->on_executor_done(NULL); + + // Verify owner_coroutine_ is cleared + EXPECT_EQ((ISrsInterruptable *)NULL, session->owner_coroutine_); + + // Clean up - set to NULL to avoid double-free + session->muxer_ = NULL; + srs_freep(mock_muxer); +} + +// Mock ISrsPackContext implementation +MockPackContext::MockPackContext() +{ +} + +MockPackContext::~MockPackContext() +{ +} + +srs_error_t MockPackContext::on_ts_message(SrsTsMessage *msg) +{ + return srs_success; +} + +void MockPackContext::on_recover_mode(int nn_recover) +{ +} + +// Test SrsGbSession::on_ps_pack method - major use scenario +VOID TEST(GB28181Test, SessionOnPsPack) +{ + // Create mock dependencies + SrsUniquePtr mock_config(new MockAppConfigForGbSession()); + MockGbMuxer *mock_muxer = new MockGbMuxer(); + + // Create session + SrsUniquePtr session(new SrsGbSession()); + + // Inject mock dependencies + session->config_ = mock_config.get(); + session->muxer_ = mock_muxer; + + // Create mock pack context + SrsUniquePtr ctx(new MockPackContext()); + ctx->media_id_ = 1; + ctx->media_startime_ = srs_time_now_realtime(); + ctx->media_nn_recovered_ = 5; + ctx->media_nn_msgs_dropped_ = 3; + ctx->media_reserved_ = 10; + + // Create test messages: 2 video messages and 1 audio message + std::vector msgs; + + // Create first video message + SrsUniquePtr video1(new SrsTsMessage()); + video1->sid_ = SrsTsPESStreamIdVideoCommon; + video1->dts_ = 90000; + video1->pts_ = 90000; + video1->payload_->append("video1", 6); + msgs.push_back(video1.get()); + + // Create audio message + SrsUniquePtr audio(new SrsTsMessage()); + audio->sid_ = SrsTsPESStreamIdAudioCommon; + audio->dts_ = 90000; + audio->pts_ = 90000; + audio->payload_->append("audio1", 6); + msgs.push_back(audio.get()); + + // Create second video message + SrsUniquePtr video2(new SrsTsMessage()); + video2->sid_ = SrsTsPESStreamIdVideoCommon; + video2->dts_ = 90000; + video2->pts_ = 90000; + video2->payload_->append("video2", 6); + msgs.push_back(video2.get()); + + // Call on_ps_pack + session->on_ps_pack(ctx.get(), NULL, msgs); + + // Verify statistics are updated correctly + EXPECT_EQ(1u, ctx->media_id_); + EXPECT_EQ(1u, session->media_id_); + EXPECT_EQ(1u, session->media_packs_); + EXPECT_EQ(3u, session->media_msgs_); + EXPECT_EQ(5u, session->media_recovered_); + EXPECT_EQ(3u, session->media_msgs_dropped_); + EXPECT_EQ(10u, session->media_reserved_); + + // Verify muxer was called (should be called twice: once for audio, once for grouped video) + EXPECT_TRUE(mock_muxer->on_ts_message_called_); + + // Reset mock for second test + mock_muxer->reset(); + + // Test context change (new media transport) + SrsUniquePtr ctx2(new MockPackContext()); + ctx2->media_id_ = 2; // Different media_id + ctx2->media_startime_ = srs_time_now_realtime(); + ctx2->media_nn_recovered_ = 2; + ctx2->media_nn_msgs_dropped_ = 1; + ctx2->media_reserved_ = 5; + + // Create new messages + std::vector msgs2; + SrsUniquePtr video3(new SrsTsMessage()); + video3->sid_ = SrsTsPESStreamIdVideoCommon; + video3->dts_ = 180000; + video3->pts_ = 180000; + video3->payload_->append("video3", 6); + msgs2.push_back(video3.get()); + + // Call on_ps_pack with new context + session->on_ps_pack(ctx2.get(), NULL, msgs2); + + // Verify statistics are accumulated for old context + EXPECT_EQ(1u, session->total_packs_); + EXPECT_EQ(3u, session->total_msgs_); + EXPECT_EQ(5u, session->total_recovered_); + EXPECT_EQ(3u, session->total_msgs_dropped_); + EXPECT_EQ(10u, session->total_reserved_); + + // Verify new context statistics + EXPECT_EQ(2u, session->media_id_); + EXPECT_EQ(1u, session->media_packs_); + EXPECT_EQ(1u, session->media_msgs_); + EXPECT_EQ(2u, session->media_recovered_); + EXPECT_EQ(1u, session->media_msgs_dropped_); + EXPECT_EQ(5u, session->media_reserved_); + + // Clean up - set to NULL to avoid double-free + session->muxer_ = NULL; + srs_freep(mock_muxer); +} + +// Mock ISrsGbMediaTcpConn implementation +MockGbMediaTcpConn::MockGbMediaTcpConn() +{ + set_cid_called_ = false; + is_connected_ = false; +} + +MockGbMediaTcpConn::~MockGbMediaTcpConn() +{ +} + +void MockGbMediaTcpConn::setup(srs_netfd_t stfd) +{ +} + +void MockGbMediaTcpConn::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) +{ +} + +bool MockGbMediaTcpConn::is_connected() +{ + return is_connected_; +} + +void MockGbMediaTcpConn::interrupt() +{ +} + +void MockGbMediaTcpConn::set_cid(const SrsContextId &cid) +{ + set_cid_called_ = true; + received_cid_ = cid; +} + +const SrsContextId &MockGbMediaTcpConn::get_id() +{ + return received_cid_; +} + +std::string MockGbMediaTcpConn::desc() +{ + return "MockGbMediaTcpConn"; +} + +srs_error_t MockGbMediaTcpConn::cycle() +{ + return srs_success; +} + +void MockGbMediaTcpConn::on_executor_done(ISrsInterruptable *executor) +{ +} + +void MockGbMediaTcpConn::reset() +{ + set_cid_called_ = false; + received_cid_ = SrsContextId(); +} + +// Test SrsGbSession::on_media_transport - covers the major use scenario: +// 1. Session receives a media transport connection +// 2. Session stores the media transport reference +// 3. Session propagates its context ID to the media transport via set_cid() +VOID TEST(GB28181Test, SessionOnMediaTransport) +{ + // Create mock dependencies + SrsUniquePtr mock_config(new MockAppConfigForGbSession()); + MockGbMuxer *mock_muxer = new MockGbMuxer(); + + // Create session + SrsUniquePtr session(new SrsGbSession()); + + // Inject mock dependencies + session->config_ = mock_config.get(); + session->muxer_ = mock_muxer; + + // Set a specific context ID for the session + SrsContextId session_cid; + session_cid.set_value("test-session-123"); + session->cid_ = session_cid; + + // Create mock media transport wrapped in SrsSharedResource + MockGbMediaTcpConn *mock_media = new MockGbMediaTcpConn(); + SrsSharedResource media_resource(mock_media); + + // Call on_media_transport + session->on_media_transport(media_resource); + + // Verify that set_cid was called on the media transport + EXPECT_TRUE(mock_media->set_cid_called_); + + // Verify that the session's context ID was passed to the media transport + EXPECT_EQ(0, mock_media->received_cid_.compare(session_cid)); + + // Clean up - set to NULL to avoid double-free + session->muxer_ = NULL; + srs_freep(mock_muxer); +} + +// Test SrsGbSession::drive_state - covers the major use scenario: +// 1. Session starts in Init state with media disconnected +// 2. When media connects, session transitions to Established state +// 3. When media disconnects, session transitions back to Init state +VOID TEST(GB28181Test, SessionDriveState) +{ + srs_error_t err; + + // Create mock dependencies + SrsUniquePtr mock_config(new MockAppConfigForGbSession()); + MockGbMuxer *mock_muxer = new MockGbMuxer(); + MockGbMediaTcpConn *mock_media = new MockGbMediaTcpConn(); + + // Create session + SrsUniquePtr session(new SrsGbSession()); + + // Inject mock dependencies + session->config_ = mock_config.get(); + session->muxer_ = mock_muxer; + session->media_ = SrsSharedResource(mock_media); + session->device_id_ = "test-device-123"; + + // Verify initial state is Init + EXPECT_EQ(SrsGbSessionStateInit, session->state_); + + // Test 1: Init state with media disconnected - should remain in Init + mock_media->is_connected_ = false; + HELPER_EXPECT_SUCCESS(session->drive_state()); + EXPECT_EQ(SrsGbSessionStateInit, session->state_); + + // Test 2: Init state with media connected - should transition to Established + mock_media->is_connected_ = true; + HELPER_EXPECT_SUCCESS(session->drive_state()); + EXPECT_EQ(SrsGbSessionStateEstablished, session->state_); + + // Test 3: Established state with media still connected - should remain in Established + mock_media->is_connected_ = true; + HELPER_EXPECT_SUCCESS(session->drive_state()); + EXPECT_EQ(SrsGbSessionStateEstablished, session->state_); + + // Test 4: Established state with media disconnected - should transition back to Init + mock_media->is_connected_ = false; + HELPER_EXPECT_SUCCESS(session->drive_state()); + EXPECT_EQ(SrsGbSessionStateInit, session->state_); + + // Clean up - set to NULL to avoid double-free + session->muxer_ = NULL; + srs_freep(mock_muxer); +} + +// Mock ISrsIpListener implementation +MockIpListener::MockIpListener() +{ + endpoint_ip_ = ""; + endpoint_port_ = 0; + label_ = ""; + set_endpoint_called_ = false; + set_label_called_ = false; +} + +MockIpListener::~MockIpListener() +{ +} + +ISrsListener *MockIpListener::set_endpoint(const std::string &i, int p) +{ + endpoint_ip_ = i; + endpoint_port_ = p; + set_endpoint_called_ = true; + return this; +} + +ISrsListener *MockIpListener::set_label(const std::string &label) +{ + label_ = label; + set_label_called_ = true; + return this; +} + +srs_error_t MockIpListener::listen() +{ + return srs_success; +} + +void MockIpListener::reset() +{ + endpoint_ip_ = ""; + endpoint_port_ = 0; + label_ = ""; + set_endpoint_called_ = false; + set_label_called_ = false; +} + +// Mock ISrsAppConfig for GbListener implementation +MockAppConfigForGbListener::MockAppConfigForGbListener() +{ + stream_caster_listen_port_ = 0; +} + +MockAppConfigForGbListener::~MockAppConfigForGbListener() +{ +} + +int MockAppConfigForGbListener::get_stream_caster_listen(SrsConfDirective *conf) +{ + return stream_caster_listen_port_; +} + +// Test SrsGbListener::initialize - covers the major use scenario: +// 1. Listener receives a configuration directive +// 2. Listener copies the configuration +// 3. Listener retrieves the listen port from config +// 4. Listener sets the endpoint (IP and port) on the media listener +// 5. Listener sets the label on the media listener +VOID TEST(GB28181Test, ListenerInitialize) +{ + srs_error_t err = srs_success; + + // Create mock dependencies + SrsUniquePtr mock_config(new MockAppConfigForGbListener()); + MockIpListener *mock_listener = new MockIpListener(); + + // Setup mock config to return test port + mock_config->stream_caster_listen_port_ = 9000; + + // Create listener + SrsUniquePtr listener(new SrsGbListener()); + + // Inject mock dependencies + listener->config_ = mock_config.get(); + listener->media_listener_ = mock_listener; + + // Create test configuration directive + SrsUniquePtr conf(new SrsConfDirective()); + conf->name_ = "stream_caster"; + conf->args_.push_back("gb28181"); + + // Add listen directive + SrsConfDirective *listen_directive = new SrsConfDirective(); + listen_directive->name_ = "listen"; + listen_directive->args_.push_back("9000"); + conf->directives_.push_back(listen_directive); + + // Test initialize() method + HELPER_EXPECT_SUCCESS(listener->initialize(conf.get())); + + // Verify configuration was copied + EXPECT_TRUE(listener->conf_ != NULL); + EXPECT_STREQ("stream_caster", listener->conf_->name_.c_str()); + + // Verify set_endpoint was called with correct parameters + EXPECT_TRUE(mock_listener->set_endpoint_called_); + EXPECT_STREQ("0.0.0.0", mock_listener->endpoint_ip_.c_str()); + EXPECT_EQ(9000, mock_listener->endpoint_port_); + + // Verify set_label was called with correct label + EXPECT_TRUE(mock_listener->set_label_called_); + EXPECT_STREQ("GB-TCP", mock_listener->label_.c_str()); + + // Clean up - set to NULL to avoid double-free + listener->media_listener_ = NULL; + srs_freep(mock_listener); +} + +// Mock ISrsHttpServeMux implementation +MockHttpServeMuxForGbListener::MockHttpServeMuxForGbListener() +{ + handle_called_ = false; + handle_pattern_ = ""; + handle_handler_ = NULL; +} + +MockHttpServeMuxForGbListener::~MockHttpServeMuxForGbListener() +{ +} + +srs_error_t MockHttpServeMuxForGbListener::handle(std::string pattern, ISrsHttpHandler *handler) +{ + handle_called_ = true; + handle_pattern_ = pattern; + handle_handler_ = handler; + return srs_success; +} + +srs_error_t MockHttpServeMuxForGbListener::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) +{ + return srs_success; +} + +void MockHttpServeMuxForGbListener::reset() +{ + handle_called_ = false; + handle_pattern_ = ""; + handle_handler_ = NULL; +} + +// Mock ISrsApiServerOwner implementation +MockApiServerOwnerForGbListener::MockApiServerOwnerForGbListener() +{ + mux_ = NULL; +} + +MockApiServerOwnerForGbListener::~MockApiServerOwnerForGbListener() +{ + mux_ = NULL; +} + +ISrsHttpServeMux *MockApiServerOwnerForGbListener::api_server() +{ + return mux_; +} + +// Mock ISrsIpListener implementation +MockIpListenerForGbListen::MockIpListenerForGbListen() +{ + listen_called_ = false; +} + +MockIpListenerForGbListen::~MockIpListenerForGbListen() +{ +} + +ISrsListener *MockIpListenerForGbListen::set_endpoint(const std::string &i, int p) +{ + return this; +} + +ISrsListener *MockIpListenerForGbListen::set_label(const std::string &label) +{ + return this; +} + +srs_error_t MockIpListenerForGbListen::listen() +{ + listen_called_ = true; + return srs_success; +} + +void MockIpListenerForGbListen::reset() +{ + listen_called_ = false; +} + +// Test SrsGbListener::listen - covers the major use scenario: +// 1. Listener calls media_listener_->listen() to start listening for TCP connections +// 2. Listener calls listen_api() to register HTTP API handlers +// 3. listen_api() retrieves the HTTP mux from api_server_owner_ +// 4. listen_api() registers the /gb/v1/publish/ endpoint with the mux +VOID TEST(GB28181Test, ListenerListen) +{ + srs_error_t err; + + // Create mock dependencies + MockIpListenerForGbListen *mock_media_listener = new MockIpListenerForGbListen(); + SrsUniquePtr mock_mux(new MockHttpServeMuxForGbListener()); + SrsUniquePtr mock_api_owner(new MockApiServerOwnerForGbListener()); + + // Setup mock api_server_owner to return mock mux + mock_api_owner->mux_ = mock_mux.get(); + + // Create listener + SrsUniquePtr listener(new SrsGbListener()); + + // Inject mock dependencies + listener->media_listener_ = mock_media_listener; + listener->api_server_owner_ = mock_api_owner.get(); + + // Create test configuration directive + SrsUniquePtr conf(new SrsConfDirective()); + conf->name_ = "stream_caster"; + conf->args_.push_back("gb28181"); + listener->conf_ = conf->copy(); + + // Test listen() method + HELPER_EXPECT_SUCCESS(listener->listen()); + + // Verify media_listener_->listen() was called + EXPECT_TRUE(mock_media_listener->listen_called_); + + // Verify mux->handle() was called with correct pattern + EXPECT_TRUE(mock_mux->handle_called_); + EXPECT_STREQ("/gb/v1/publish/", mock_mux->handle_pattern_.c_str()); + EXPECT_TRUE(mock_mux->handle_handler_ != NULL); + + // Clean up - set to NULL to avoid double-free + listener->media_listener_ = NULL; + srs_freep(mock_media_listener); +} + +// Mock ISrsInterruptable for testing SrsGbMediaTcpConn::setup_owner +class MockInterruptableForGbMediaTcpConn : public ISrsInterruptable +{ +public: + bool interrupt_called_; + srs_error_t pull_error_; + +public: + MockInterruptableForGbMediaTcpConn() + { + interrupt_called_ = false; + pull_error_ = srs_success; + } + virtual ~MockInterruptableForGbMediaTcpConn() + { + srs_freep(pull_error_); + } + +public: + virtual void interrupt() + { + interrupt_called_ = true; + } + virtual srs_error_t pull() + { + return srs_error_copy(pull_error_); + } +}; + +// Mock ISrsContextIdSetter for testing SrsGbMediaTcpConn::setup_owner +class MockContextIdSetterForGbMediaTcpConn : public ISrsContextIdSetter +{ +public: + bool set_cid_called_; + SrsContextId received_cid_; + +public: + MockContextIdSetterForGbMediaTcpConn() + { + set_cid_called_ = false; + } + virtual ~MockContextIdSetterForGbMediaTcpConn() + { + } + +public: + virtual void set_cid(const SrsContextId &cid) + { + set_cid_called_ = true; + received_cid_ = cid; + } +}; + +// Test SrsGbMediaTcpConn::setup_owner, on_executor_done, is_connected, set_cid, get_id, desc +// This test covers the major use scenario: +// 1. Create SrsGbMediaTcpConn and setup owner with wrapper, coroutine, and cid setter +// 2. Verify is_connected() returns false initially +// 3. Call set_cid() and verify it propagates to owner_cid_ +// 4. Verify get_id() returns the correct context id +// 5. Verify desc() returns correct description +// 6. Call on_executor_done() and verify owner_coroutine_ is cleared +VOID TEST(GbMediaTcpConnTest, SetupOwnerAndLifecycle) +{ + // Create mock dependencies + SrsUniquePtr mock_coroutine(new MockInterruptableForGbMediaTcpConn()); + SrsUniquePtr mock_cid_setter(new MockContextIdSetterForGbMediaTcpConn()); + + // Create SrsGbMediaTcpConn - wrapper will own it + SrsGbMediaTcpConn *conn = new SrsGbMediaTcpConn(); + + // Create a wrapper that owns the conn + SrsUniquePtr > wrapper(new SrsSharedResource(conn)); + + // Test setup_owner + conn->setup_owner(wrapper.get(), mock_coroutine.get(), mock_cid_setter.get()); + + // Verify is_connected() returns false initially + EXPECT_FALSE(conn->is_connected()); + + // Test set_cid() - should propagate to owner_cid_ + SrsContextId test_cid; + conn->set_cid(test_cid); + + // Verify owner_cid_->set_cid() was called + EXPECT_TRUE(mock_cid_setter->set_cid_called_); + EXPECT_EQ(0, test_cid.compare(mock_cid_setter->received_cid_)); + + // Test get_id() - should return the cid we set + const SrsContextId &returned_cid = conn->get_id(); + EXPECT_EQ(0, test_cid.compare(returned_cid)); + + // Test desc() - should return "GB-Media-TCP" + EXPECT_STREQ("GB-Media-TCP", conn->desc().c_str()); + + // Test on_executor_done() - should clear owner_coroutine_ + conn->on_executor_done(mock_coroutine.get()); + // After on_executor_done, owner_coroutine_ should be NULL + // We can't directly verify this, but we can verify the method doesn't crash + + // wrapper will automatically clean up conn when it goes out of scope +} + +// Mock ISrsGbSession implementation +MockGbSessionForMediaConn::MockGbSessionForMediaConn() +{ + on_ps_pack_called_ = false; + received_pack_ = NULL; + received_ps_ = NULL; + on_media_transport_called_ = false; +} + +MockGbSessionForMediaConn::~MockGbSessionForMediaConn() +{ +} + +void MockGbSessionForMediaConn::setup(SrsConfDirective *conf) +{ +} + +void MockGbSessionForMediaConn::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) +{ +} + +void MockGbSessionForMediaConn::on_media_transport(SrsSharedResource media) +{ + on_media_transport_called_ = true; + received_media_ = media; +} + +void MockGbSessionForMediaConn::on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs) +{ + on_ps_pack_called_ = true; + received_pack_ = ctx; + received_ps_ = ps; + received_msgs_ = msgs; +} + +const SrsContextId &MockGbSessionForMediaConn::get_id() +{ + static SrsContextId cid; + return cid; +} + +std::string MockGbSessionForMediaConn::desc() +{ + return "MockGbSessionForMediaConn"; +} + +srs_error_t MockGbSessionForMediaConn::cycle() +{ + return srs_success; +} + +void MockGbSessionForMediaConn::on_executor_done(ISrsInterruptable *executor) +{ +} + +void MockGbSessionForMediaConn::reset() +{ + on_ps_pack_called_ = false; + received_pack_ = NULL; + received_ps_ = NULL; + received_msgs_.clear(); + on_media_transport_called_ = false; +} + +// Mock ISrsResourceManager implementation +MockResourceManagerForBindSession::MockResourceManagerForBindSession() +{ + session_to_return_ = NULL; +} + +MockResourceManagerForBindSession::~MockResourceManagerForBindSession() +{ +} + +srs_error_t MockResourceManagerForBindSession::start() +{ + return srs_success; +} + +bool MockResourceManagerForBindSession::empty() +{ + return true; +} + +size_t MockResourceManagerForBindSession::size() +{ + return 0; +} + +void MockResourceManagerForBindSession::add(ISrsResource *conn, bool *exists) +{ +} + +void MockResourceManagerForBindSession::add_with_id(const std::string &id, ISrsResource *conn) +{ +} + +void MockResourceManagerForBindSession::add_with_fast_id(uint64_t id, ISrsResource *conn) +{ +} + +ISrsResource *MockResourceManagerForBindSession::at(int index) +{ + return NULL; +} + +ISrsResource *MockResourceManagerForBindSession::find_by_id(std::string id) +{ + return NULL; +} + +ISrsResource *MockResourceManagerForBindSession::find_by_fast_id(uint64_t id) +{ + return session_to_return_; +} + +ISrsResource *MockResourceManagerForBindSession::find_by_name(std::string /*name*/) +{ + return NULL; +} + +void MockResourceManagerForBindSession::remove(ISrsResource *c) +{ +} + +void MockResourceManagerForBindSession::subscribe(ISrsDisposingHandler *h) +{ +} + +void MockResourceManagerForBindSession::unsubscribe(ISrsDisposingHandler *h) +{ +} + +void MockResourceManagerForBindSession::reset() +{ + session_to_return_ = NULL; +} + +// Test SrsGbMediaTcpConn::on_ps_pack - covers the major use scenario: +// 1. Media connection receives PS pack with messages +// 2. Connection state changes from disconnected to connected +// 3. Session is notified about the PS pack via on_ps_pack callback +VOID TEST(GbMediaTcpConnTest, OnPsPack) +{ + // Create mock dependencies + MockGbSessionForMediaConn *mock_session = new MockGbSessionForMediaConn(); + MockPackContext *mock_pack = new MockPackContext(); + + // Create SrsGbMediaTcpConn - wrapper will own it + SrsGbMediaTcpConn *conn = new SrsGbMediaTcpConn(); + + // Create a wrapper that owns the conn + SrsUniquePtr > wrapper(new SrsSharedResource(conn)); + + // Inject mock dependencies + conn->session_ = mock_session; + conn->pack_ = mock_pack; + + // Verify initial state - not connected + EXPECT_FALSE(conn->is_connected()); + + // Create test messages: 1 video message and 1 audio message + std::vector msgs; + + // Create video message + SrsUniquePtr video(new SrsTsMessage()); + video->sid_ = SrsTsPESStreamIdVideoCommon; + video->dts_ = 90000; + video->pts_ = 90000; + video->payload_->append("video_data", 10); + msgs.push_back(video.get()); + + // Create audio message + SrsUniquePtr audio(new SrsTsMessage()); + audio->sid_ = SrsTsPESStreamIdAudioCommon; + audio->dts_ = 90000; + audio->pts_ = 90000; + audio->payload_->append("audio_data", 10); + msgs.push_back(audio.get()); + + // Call on_ps_pack + srs_error_t err = conn->on_ps_pack(NULL, msgs); + HELPER_EXPECT_SUCCESS(err); + + // Verify connection state changed to connected + EXPECT_TRUE(conn->is_connected()); + + // Verify session->on_ps_pack was called + EXPECT_TRUE(mock_session->on_ps_pack_called_); + EXPECT_EQ(mock_pack, mock_session->received_pack_); + EXPECT_EQ(NULL, mock_session->received_ps_); + EXPECT_EQ(2u, mock_session->received_msgs_.size()); + + // Clean up - set to NULL to avoid double-free + // Note: conn destructor will free pack_ and session_, so we set them to NULL + conn->session_ = NULL; + conn->pack_ = NULL; + srs_freep(mock_session); + srs_freep(mock_pack); +} + +// Test SrsGbMediaTcpConn::bind_session - covers the major use scenario: +// 1. Media connection binds to an existing session by SSRC +// 2. Session is found in the resource manager by fast_id (SSRC) +// 3. Session's on_media_transport is called with the media connection wrapper +// 4. The session pointer is returned to the caller +VOID TEST(GbMediaTcpConnTest, BindSession) +{ + srs_error_t err = srs_success; + + // Create mock session wrapped in SrsSharedResource + MockGbSessionForMediaConn *mock_session = new MockGbSessionForMediaConn(); + SrsUniquePtr > session_wrapper(new SrsSharedResource(mock_session)); + + // Create mock resource manager + SrsUniquePtr mock_manager(new MockResourceManagerForBindSession()); + mock_manager->session_to_return_ = session_wrapper.get(); + + // Create SrsGbMediaTcpConn - wrapper will own it + SrsGbMediaTcpConn *conn = new SrsGbMediaTcpConn(); + SrsUniquePtr > wrapper(new SrsSharedResource(conn)); + + // Inject mock dependencies + conn->gb_manager_ = mock_manager.get(); + conn->wrapper_ = wrapper.get(); + + // Test bind_session with valid SSRC + uint32_t ssrc = 12345; + ISrsGbSession *bound_session = NULL; + err = conn->bind_session(ssrc, &bound_session); + HELPER_EXPECT_SUCCESS(err); + + // Verify session was found and bound + EXPECT_TRUE(bound_session != NULL); + EXPECT_EQ(mock_session, bound_session); + + // Verify session's on_media_transport was called with the wrapper + EXPECT_TRUE(mock_session->on_media_transport_called_); + EXPECT_EQ(conn, mock_session->received_media_.get()); + + // Test bind_session with zero SSRC (should return success but do nothing) + mock_session->reset(); + bound_session = NULL; + err = conn->bind_session(0, &bound_session); + HELPER_EXPECT_SUCCESS(err); + EXPECT_TRUE(bound_session == NULL); + EXPECT_FALSE(mock_session->on_media_transport_called_); + + // Test bind_session when session not found (should return success but do nothing) + mock_session->reset(); + mock_manager->session_to_return_ = NULL; + bound_session = NULL; + err = conn->bind_session(ssrc, &bound_session); + HELPER_EXPECT_SUCCESS(err); + EXPECT_TRUE(bound_session == NULL); + EXPECT_FALSE(mock_session->on_media_transport_called_); + + // Clean up - set to NULL to avoid double-free + conn->gb_manager_ = NULL; + conn->wrapper_ = NULL; +} + +// Test SrsMpegpsQueue::push - covers the major use scenario: +// 1. Push audio and video packets with unique timestamps +// 2. Handle timestamp collision by adjusting timestamp (+1ms) +// 3. Verify audio/video counters are updated correctly +VOID TEST(MpegpsQueueTest, PushMediaPackets) +{ + srs_error_t err = srs_success; + + // Create SrsMpegpsQueue + SrsUniquePtr queue(new SrsMpegpsQueue()); + + // Test 1: Push video packet with unique timestamp + SrsMediaPacket *video1 = new SrsMediaPacket(); + video1->timestamp_ = 1000; + video1->message_type_ = SrsFrameTypeVideo; + char *video_data1 = new char[10]; + memset(video_data1, 0x01, 10); + video1->wrap(video_data1, 10); + + err = queue->push(video1); + HELPER_EXPECT_SUCCESS(err); + EXPECT_EQ(1, queue->nb_videos_); + EXPECT_EQ(0, queue->nb_audios_); + EXPECT_EQ(1u, queue->msgs_.size()); + EXPECT_EQ(video1, queue->msgs_[1000]); + + // Test 2: Push audio packet with unique timestamp + SrsMediaPacket *audio1 = new SrsMediaPacket(); + audio1->timestamp_ = 1020; + audio1->message_type_ = SrsFrameTypeAudio; + char *audio_data1 = new char[8]; + memset(audio_data1, 0xAF, 8); + audio1->wrap(audio_data1, 8); + + err = queue->push(audio1); + HELPER_EXPECT_SUCCESS(err); + EXPECT_EQ(1, queue->nb_videos_); + EXPECT_EQ(1, queue->nb_audios_); + EXPECT_EQ(2u, queue->msgs_.size()); + EXPECT_EQ(audio1, queue->msgs_[1020]); + + // Test 3: Push video packet with timestamp collision - should adjust to 1001 + SrsMediaPacket *video2 = new SrsMediaPacket(); + video2->timestamp_ = 1000; // Same as video1 + video2->message_type_ = SrsFrameTypeVideo; + char *video_data2 = new char[10]; + memset(video_data2, 0x02, 10); + video2->wrap(video_data2, 10); + + err = queue->push(video2); + HELPER_EXPECT_SUCCESS(err); + EXPECT_EQ(2, queue->nb_videos_); + EXPECT_EQ(1, queue->nb_audios_); + EXPECT_EQ(3u, queue->msgs_.size()); + // Timestamp should be adjusted to 1001 + EXPECT_EQ(1001, video2->timestamp_); + EXPECT_EQ(video2, queue->msgs_[1001]); + + // Test 4: Push audio packet with timestamp collision - should adjust to 1021 + SrsMediaPacket *audio2 = new SrsMediaPacket(); + audio2->timestamp_ = 1020; // Same as audio1 + audio2->message_type_ = SrsFrameTypeAudio; + char *audio_data2 = new char[8]; + memset(audio_data2, 0xAE, 8); + audio2->wrap(audio_data2, 8); + + err = queue->push(audio2); + HELPER_EXPECT_SUCCESS(err); + EXPECT_EQ(2, queue->nb_videos_); + EXPECT_EQ(2, queue->nb_audios_); + EXPECT_EQ(4u, queue->msgs_.size()); + // Timestamp should be adjusted to 1021 + EXPECT_EQ(1021, audio2->timestamp_); + EXPECT_EQ(audio2, queue->msgs_[1021]); + + // Test 5: Push video packet with multiple collisions - should adjust to 1002 + SrsMediaPacket *video3 = new SrsMediaPacket(); + video3->timestamp_ = 1000; // Will collide with 1000 and 1001 + video3->message_type_ = SrsFrameTypeVideo; + char *video_data3 = new char[10]; + memset(video_data3, 0x03, 10); + video3->wrap(video_data3, 10); + + err = queue->push(video3); + HELPER_EXPECT_SUCCESS(err); + EXPECT_EQ(3, queue->nb_videos_); + EXPECT_EQ(2, queue->nb_audios_); + EXPECT_EQ(5u, queue->msgs_.size()); + // Timestamp should be adjusted to 1002 + EXPECT_EQ(1002, video3->timestamp_); + EXPECT_EQ(video3, queue->msgs_[1002]); + + // Verify all packets are in the queue with correct timestamps + EXPECT_EQ(video1, queue->msgs_[1000]); + EXPECT_EQ(video2, queue->msgs_[1001]); + EXPECT_EQ(video3, queue->msgs_[1002]); + EXPECT_EQ(audio1, queue->msgs_[1020]); + EXPECT_EQ(audio2, queue->msgs_[1021]); +} + +// Test SrsMpegpsQueue::dequeue - covers the major use scenario: +// 1. Dequeue returns NULL when there are insufficient packets (< 2 videos or < 2 audios) +// 2. Dequeue returns packets in timestamp order when there are 2+ videos and 2+ audios +// 3. Verify audio/video counters are decremented correctly +// 4. Verify packets are removed from the queue in correct order +VOID TEST(MpegpsQueueTest, DequeueMediaPackets) +{ + srs_error_t err = srs_success; + + // Create SrsMpegpsQueue + SrsUniquePtr queue(new SrsMpegpsQueue()); + + // Test 1: Dequeue returns NULL when queue is empty + SrsMediaPacket *msg = queue->dequeue(); + EXPECT_TRUE(msg == NULL); + + // Test 2: Push 1 video and 1 audio - should not dequeue (need 2+ of each) + SrsMediaPacket *video1 = new SrsMediaPacket(); + video1->timestamp_ = 1000; + video1->message_type_ = SrsFrameTypeVideo; + char *video_data1 = new char[10]; + memset(video_data1, 0x01, 10); + video1->wrap(video_data1, 10); + err = queue->push(video1); + HELPER_EXPECT_SUCCESS(err); + + SrsMediaPacket *audio1 = new SrsMediaPacket(); + audio1->timestamp_ = 1020; + audio1->message_type_ = SrsFrameTypeAudio; + char *audio_data1 = new char[8]; + memset(audio_data1, 0xAF, 8); + audio1->wrap(audio_data1, 8); + err = queue->push(audio1); + HELPER_EXPECT_SUCCESS(err); + + // Should return NULL - need 2+ videos and 2+ audios + msg = queue->dequeue(); + EXPECT_TRUE(msg == NULL); + EXPECT_EQ(1, queue->nb_videos_); + EXPECT_EQ(1, queue->nb_audios_); + + // Test 3: Push another video and audio - now should dequeue + SrsMediaPacket *video2 = new SrsMediaPacket(); + video2->timestamp_ = 1040; + video2->message_type_ = SrsFrameTypeVideo; + char *video_data2 = new char[10]; + memset(video_data2, 0x02, 10); + video2->wrap(video_data2, 10); + err = queue->push(video2); + HELPER_EXPECT_SUCCESS(err); + + SrsMediaPacket *audio2 = new SrsMediaPacket(); + audio2->timestamp_ = 1060; + audio2->message_type_ = SrsFrameTypeAudio; + char *audio_data2 = new char[8]; + memset(audio_data2, 0xAE, 8); + audio2->wrap(audio_data2, 8); + err = queue->push(audio2); + HELPER_EXPECT_SUCCESS(err); + + // Now we have 2 videos and 2 audios - should dequeue in timestamp order + EXPECT_EQ(2, queue->nb_videos_); + EXPECT_EQ(2, queue->nb_audios_); + EXPECT_EQ(4u, queue->msgs_.size()); + + // Test 4: Dequeue first packet (video1 at timestamp 1000) + msg = queue->dequeue(); + EXPECT_TRUE(msg != NULL); + EXPECT_EQ(1000, msg->timestamp_); + EXPECT_TRUE(msg->is_video()); + EXPECT_EQ(1, queue->nb_videos_); + EXPECT_EQ(2, queue->nb_audios_); + EXPECT_EQ(3u, queue->msgs_.size()); + srs_freep(msg); + + // Test 5: After first dequeue, we have 1 video and 2 audios - should return NULL + msg = queue->dequeue(); + EXPECT_TRUE(msg == NULL); + EXPECT_EQ(1, queue->nb_videos_); + EXPECT_EQ(2, queue->nb_audios_); + EXPECT_EQ(3u, queue->msgs_.size()); + + // Test 6: Push more videos to reach 2+ videos again + SrsMediaPacket *video3 = new SrsMediaPacket(); + video3->timestamp_ = 1080; + video3->message_type_ = SrsFrameTypeVideo; + char *video_data3 = new char[10]; + memset(video_data3, 0x03, 10); + video3->wrap(video_data3, 10); + err = queue->push(video3); + HELPER_EXPECT_SUCCESS(err); + + // Now we have 2 videos and 2 audios again - should dequeue + EXPECT_EQ(2, queue->nb_videos_); + EXPECT_EQ(2, queue->nb_audios_); + EXPECT_EQ(4u, queue->msgs_.size()); + + // Dequeue second packet (audio1 at timestamp 1020) + msg = queue->dequeue(); + EXPECT_TRUE(msg != NULL); + EXPECT_EQ(1020, msg->timestamp_); + EXPECT_TRUE(msg->is_audio()); + EXPECT_EQ(2, queue->nb_videos_); + EXPECT_EQ(1, queue->nb_audios_); + EXPECT_EQ(3u, queue->msgs_.size()); + srs_freep(msg); +} + +// Mock ISrsBasicRtmpClient implementation +MockGbRtmpClient::MockGbRtmpClient() +{ + connect_called_ = false; + publish_called_ = false; + close_called_ = false; + connect_error_ = srs_success; + publish_error_ = srs_success; + stream_id_ = 1; +} + +MockGbRtmpClient::~MockGbRtmpClient() +{ + srs_freep(connect_error_); + srs_freep(publish_error_); +} + +srs_error_t MockGbRtmpClient::connect() +{ + connect_called_ = true; + return srs_error_copy(connect_error_); +} + +void MockGbRtmpClient::close() +{ + close_called_ = true; +} + +srs_error_t MockGbRtmpClient::publish(int chunk_size, bool with_vhost, std::string *pstream) +{ + publish_called_ = true; + return srs_error_copy(publish_error_); +} + +srs_error_t MockGbRtmpClient::play(int chunk_size, bool with_vhost, std::string *pstream) +{ + return srs_success; +} + +void MockGbRtmpClient::kbps_sample(const char *label, srs_utime_t age) +{ +} + +int MockGbRtmpClient::sid() +{ + return stream_id_; +} + +srs_error_t MockGbRtmpClient::recv_message(SrsRtmpCommonMessage **pmsg) +{ + return srs_success; +} + +srs_error_t MockGbRtmpClient::decode_message(SrsRtmpCommonMessage *msg, SrsRtmpCommand **ppacket) +{ + return srs_success; +} + +srs_error_t MockGbRtmpClient::send_and_free_messages(SrsMediaPacket **msgs, int nb_msgs) +{ + return srs_success; +} + +srs_error_t MockGbRtmpClient::send_and_free_message(SrsMediaPacket *msg) +{ + return srs_success; +} + +void MockGbRtmpClient::set_recv_timeout(srs_utime_t timeout) +{ +} + +void MockGbRtmpClient::reset() +{ + connect_called_ = false; + publish_called_ = false; + close_called_ = false; + srs_freep(connect_error_); + srs_freep(publish_error_); +} + +// Mock ISrsRawAacStream implementation +MockGbRawAacStream::MockGbRawAacStream() +{ + adts_demux_called_ = false; + mux_sequence_header_called_ = false; + mux_aac2flv_called_ = false; + adts_demux_error_ = srs_success; + mux_sequence_header_error_ = srs_success; + mux_aac2flv_error_ = srs_success; + sequence_header_output_ = "\xAF\x00\x12\x10"; // AAC sequence header + demux_frame_size_ = 100; +} + +MockGbRawAacStream::~MockGbRawAacStream() +{ + srs_freep(adts_demux_error_); + srs_freep(mux_sequence_header_error_); + srs_freep(mux_aac2flv_error_); +} + +srs_error_t MockGbRawAacStream::adts_demux(SrsBuffer *stream, char **pframe, int *pnb_frame, SrsRawAacStreamCodec &codec) +{ + adts_demux_called_ = true; + + if (adts_demux_error_ != srs_success) { + return srs_error_copy(adts_demux_error_); + } + + // Simulate demuxing AAC frame + if (!stream->empty()) { + *pframe = stream->data() + stream->pos(); + *pnb_frame = srs_min(demux_frame_size_, stream->left()); + stream->skip(*pnb_frame); + + // Set codec info + codec.aac_object_ = SrsAacObjectTypeAacLC; + codec.sampling_frequency_index_ = 4; // 44100Hz + codec.channel_configuration_ = 2; // Stereo + codec.sound_format_ = 10; // AAC + codec.sound_rate_ = 3; // 44kHz + codec.sound_size_ = 1; // 16-bit + codec.sound_type_ = 1; // Stereo + } else { + *pframe = NULL; + *pnb_frame = 0; + } + + return srs_success; +} + +srs_error_t MockGbRawAacStream::mux_sequence_header(SrsRawAacStreamCodec *codec, std::string &sh) +{ + mux_sequence_header_called_ = true; + + if (mux_sequence_header_error_ != srs_success) { + return srs_error_copy(mux_sequence_header_error_); + } + + sh = sequence_header_output_; + return srs_success; +} + +srs_error_t MockGbRawAacStream::mux_aac2flv(char *frame, int nb_frame, SrsRawAacStreamCodec *codec, uint32_t dts, char **flv, int *nb_flv) +{ + mux_aac2flv_called_ = true; + + if (mux_aac2flv_error_ != srs_success) { + return srs_error_copy(mux_aac2flv_error_); + } + + // Simulate muxing AAC to FLV + *nb_flv = nb_frame + 2; // 2 bytes for AAC header + *flv = new char[*nb_flv]; + (*flv)[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo + (*flv)[1] = codec->aac_packet_type_; // 0 for sequence header, 1 for raw data + if (nb_frame > 0) { + memcpy(*flv + 2, frame, nb_frame); + } + + return srs_success; +} + +void MockGbRawAacStream::reset() +{ + adts_demux_called_ = false; + mux_sequence_header_called_ = false; + mux_aac2flv_called_ = false; + srs_freep(adts_demux_error_); + srs_freep(mux_sequence_header_error_); + srs_freep(mux_aac2flv_error_); +} + +// Mock ISrsMpegpsQueue implementation +MockGbMpegpsQueue::MockGbMpegpsQueue() +{ + push_called_ = false; + push_error_ = srs_success; + push_count_ = 0; +} + +MockGbMpegpsQueue::~MockGbMpegpsQueue() +{ + srs_freep(push_error_); +} + +srs_error_t MockGbMpegpsQueue::push(SrsMediaPacket *msg) +{ + push_called_ = true; + push_count_++; + + if (push_error_ != srs_success) { + return srs_error_copy(push_error_); + } + + return srs_success; +} + +SrsMediaPacket *MockGbMpegpsQueue::dequeue() +{ + return NULL; +} + +void MockGbMpegpsQueue::reset() +{ + push_called_ = false; + push_count_ = 0; + srs_freep(push_error_); +} + +// Mock ISrsGbSession implementation +MockGbSessionForMuxer::MockGbSessionForMuxer() +{ + device_id_ = "34020000001320000001"; +} + +MockGbSessionForMuxer::~MockGbSessionForMuxer() +{ +} + +void MockGbSessionForMuxer::setup(SrsConfDirective *conf) +{ +} + +void MockGbSessionForMuxer::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) +{ +} + +void MockGbSessionForMuxer::on_media_transport(SrsSharedResource media) +{ +} + +void MockGbSessionForMuxer::on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs) +{ +} + +const SrsContextId &MockGbSessionForMuxer::get_id() +{ + static SrsContextId cid; + return cid; +} + +std::string MockGbSessionForMuxer::desc() +{ + return "MockGbSessionForMuxer"; +} + +srs_error_t MockGbSessionForMuxer::cycle() +{ + return srs_success; +} + +void MockGbSessionForMuxer::on_executor_done(ISrsInterruptable *executor) +{ +} + +// Mock ISrsPsPackHandler implementation +MockPsPackHandler::MockPsPackHandler() +{ + on_ps_pack_called_ = false; + on_ps_pack_count_ = 0; + last_pack_id_ = 0; + last_msgs_count_ = 0; + on_ps_pack_error_ = srs_success; +} + +MockPsPackHandler::~MockPsPackHandler() +{ + srs_freep(on_ps_pack_error_); +} + +srs_error_t MockPsPackHandler::on_ps_pack(SrsPsPacket *ps, const std::vector &msgs) +{ + on_ps_pack_called_ = true; + on_ps_pack_count_++; + last_pack_id_ = ps->id_; + last_msgs_count_ = (int)msgs.size(); + + if (on_ps_pack_error_ != srs_success) { + return srs_error_copy(on_ps_pack_error_); + } + + return srs_success; +} + +void MockPsPackHandler::reset() +{ + on_ps_pack_called_ = false; + on_ps_pack_count_ = 0; + last_pack_id_ = 0; + last_msgs_count_ = 0; + srs_freep(on_ps_pack_error_); +} + +// Test SrsGbMuxer::on_ts_message - covers the major use scenario: +// 1. Process audio TS message with AAC ADTS data +// 2. Connect to RTMP server on first message +// 3. Generate AAC sequence header on first audio frame +// 4. Mux audio frames to FLV and push to queue +VOID TEST(GbMuxerTest, OnTsMessageAudio) +{ + srs_error_t err = srs_success; + + // Create mock session + SrsUniquePtr mock_session(new MockGbSessionForMuxer()); + mock_session->device_id_ = "34020000001320000001"; + + // Create SrsGbMuxer + SrsUniquePtr muxer(new SrsGbMuxer(mock_session.get())); + muxer->setup("rtmp://127.0.0.1/live/[stream]"); + + // Create mock dependencies + MockGbRawAacStream *mock_aac = new MockGbRawAacStream(); + MockGbMpegpsQueue *mock_queue = new MockGbMpegpsQueue(); + + // Inject mocks into muxer (but not sdk_ yet - let connect() create it) + muxer->aac_ = mock_aac; + muxer->queue_ = mock_queue; + + // We need to mock the app_factory to return our mock SDK + // For now, let's just test without RTMP connection by setting sdk_ to a mock + // that's already "connected" + MockGbRtmpClient *mock_sdk = new MockGbRtmpClient(); + muxer->sdk_ = mock_sdk; // Simulate already connected + + // Create TS message with audio data (AAC ADTS format) + SrsUniquePtr ts_msg(new SrsTsMessage()); + ts_msg->sid_ = SrsTsPESStreamIdAudioCommon; // Audio stream + ts_msg->dts_ = 90000; // 1 second in 90kHz timebase + + // Create AAC ADTS frame data (simplified) + // ADTS header (7 bytes) + AAC raw data + char adts_data[200]; + memset(adts_data, 0, sizeof(adts_data)); + // ADTS sync word (12 bits = 0xFFF) + adts_data[0] = 0xFF; + adts_data[1] = 0xF1; // MPEG-4, no CRC + // Profile, sample rate, channel config (simplified) + adts_data[2] = 0x50; // AAC LC, 44.1kHz + adts_data[3] = 0x80; + adts_data[4] = 0x00; + adts_data[5] = 0x1F; + adts_data[6] = 0xFC; + // Fill with some audio data + for (int i = 7; i < 200; i++) { + adts_data[i] = (char)(i & 0xFF); + } + + ts_msg->payload_->append(adts_data, 200); + + // Test: Process audio TS message + err = muxer->on_ts_message(ts_msg.get()); + HELPER_EXPECT_SUCCESS(err); + + // Verify: AAC demux was called + EXPECT_TRUE(mock_aac->adts_demux_called_); + + // Verify: AAC sequence header was generated + EXPECT_TRUE(mock_aac->mux_sequence_header_called_); + + // Verify: AAC to FLV muxing was called (sequence header + raw data) + EXPECT_TRUE(mock_aac->mux_aac2flv_called_); + + // Verify: Messages were pushed to queue (sequence header + raw data) + EXPECT_TRUE(mock_queue->push_called_); + EXPECT_GE(mock_queue->push_count_, 2); // At least sequence header + one raw frame + + // Clean up - set to NULL to avoid double-free + muxer->sdk_ = NULL; + muxer->aac_ = NULL; + muxer->queue_ = NULL; + srs_freep(mock_sdk); + srs_freep(mock_aac); + srs_freep(mock_queue); +} + +// Test SrsPackContext::on_ts_message - covers the major use scenario: +// 1. Accumulate multiple TS messages in the same PS pack +// 2. Trigger on_ps_pack callback when a new pack ID is detected +// 3. Correct DTS/PTS timestamps when they are zero +VOID TEST(PackContextTest, OnTsMessageMultiplePacksWithTimestampCorrection) +{ + srs_error_t err = srs_success; + + // Create mock handler + SrsUniquePtr mock_handler(new MockPsPackHandler()); + + // Create SrsPackContext + SrsUniquePtr ctx(new SrsPackContext(mock_handler.get())); + + // Create PS context and packet for first pack + SrsUniquePtr ps_ctx1(new SrsPsContext()); + SrsUniquePtr ps_packet1(new SrsPsPacket(ps_ctx1.get())); + ps_packet1->id_ = 0x10000001; // First pack ID + + // Create first TS message (video) with valid timestamps + SrsUniquePtr msg1(new SrsTsMessage()); + msg1->sid_ = SrsTsPESStreamIdVideoCommon; + msg1->dts_ = 90000; // 1 second in 90kHz + msg1->pts_ = 90000; + msg1->ps_helper_ = &ps_ctx1->helper_; + ps_ctx1->helper_.ctx_ = ps_ctx1.get(); + ps_ctx1->helper_.ps_ = ps_packet1.get(); + + // Test: Process first message + err = ctx->on_ts_message(msg1.get()); + HELPER_EXPECT_SUCCESS(err); + + // Verify: Handler not called yet (still accumulating messages in same pack) + EXPECT_FALSE(mock_handler->on_ps_pack_called_); + + // Create second TS message (audio) in same pack with zero timestamps + SrsUniquePtr msg2(new SrsTsMessage()); + msg2->sid_ = SrsTsPESStreamIdAudioCommon; + msg2->dts_ = 0; // Zero timestamp - should be corrected + msg2->pts_ = 0; // Zero timestamp - should be corrected + msg2->ps_helper_ = &ps_ctx1->helper_; + + // Test: Process second message in same pack + err = ctx->on_ts_message(msg2.get()); + HELPER_EXPECT_SUCCESS(err); + + // Verify: Handler still not called (same pack ID) + EXPECT_FALSE(mock_handler->on_ps_pack_called_); + + // Create PS context and packet for second pack (different ID) + SrsUniquePtr ps_ctx2(new SrsPsContext()); + SrsUniquePtr ps_packet2(new SrsPsPacket(ps_ctx2.get())); + ps_packet2->id_ = 0x10000002; // Different pack ID + + // Create third TS message (video) in new pack + SrsUniquePtr msg3(new SrsTsMessage()); + msg3->sid_ = SrsTsPESStreamIdVideoCommon; + msg3->dts_ = 180000; // 2 seconds in 90kHz + msg3->pts_ = 180000; + msg3->ps_helper_ = &ps_ctx2->helper_; + ps_ctx2->helper_.ctx_ = ps_ctx2.get(); + ps_ctx2->helper_.ps_ = ps_packet2.get(); + + // Test: Process third message with new pack ID + err = ctx->on_ts_message(msg3.get()); + HELPER_EXPECT_SUCCESS(err); + + // Verify: Handler was called once (pack ID changed) + EXPECT_TRUE(mock_handler->on_ps_pack_called_); + EXPECT_EQ(1, mock_handler->on_ps_pack_count_); + EXPECT_EQ(0x10000001u, mock_handler->last_pack_id_); + EXPECT_EQ(2, mock_handler->last_msgs_count_); // msg1 and msg2 +} + +// Test SrsPackContext::on_recover_mode - covers recovery statistics and message dropping +VOID TEST(PackContextTest, OnRecoverMode) +{ + srs_error_t err; + + // Create mock handler + SrsUniquePtr mock_handler(new MockPsPackHandler()); + + // Create SrsPackContext + SrsUniquePtr ctx(new SrsPackContext(mock_handler.get())); + + // Verify initial state + EXPECT_EQ(0u, ctx->media_nn_recovered_); + EXPECT_EQ(0u, ctx->media_nn_msgs_dropped_); + + // Test: First recovery with empty message queue (nn_recover = 1) + ctx->on_recover_mode(1); + + // Verify: Recovery counter incremented, no messages dropped + EXPECT_EQ(1u, ctx->media_nn_recovered_); + EXPECT_EQ(0u, ctx->media_nn_msgs_dropped_); + + // Setup: Add messages to the queue + SrsUniquePtr ps_ctx(new SrsPsContext()); + SrsUniquePtr ps_packet(new SrsPsPacket(ps_ctx.get())); + ps_packet->id_ = 0x10000001; + + // Create and add first message + SrsUniquePtr msg1(new SrsTsMessage()); + msg1->sid_ = SrsTsPESStreamIdVideoCommon; + msg1->dts_ = 90000; + msg1->pts_ = 90000; + msg1->ps_helper_ = &ps_ctx->helper_; + ps_ctx->helper_.ctx_ = ps_ctx.get(); + ps_ctx->helper_.ps_ = ps_packet.get(); + + err = ctx->on_ts_message(msg1.get()); + HELPER_EXPECT_SUCCESS(err); + + // Create and add second message + SrsUniquePtr msg2(new SrsTsMessage()); + msg2->sid_ = SrsTsPESStreamIdAudioCommon; + msg2->dts_ = 90000; + msg2->pts_ = 90000; + msg2->ps_helper_ = &ps_ctx->helper_; + + err = ctx->on_ts_message(msg2.get()); + HELPER_EXPECT_SUCCESS(err); + + // Test: Second recovery with messages in queue (nn_recover = 2) + ctx->on_recover_mode(2); + + // Verify: Recovery counter NOT incremented (nn_recover > 1), but messages dropped + EXPECT_EQ(1u, ctx->media_nn_recovered_); // Still 1, not incremented + EXPECT_EQ(2u, ctx->media_nn_msgs_dropped_); // 2 messages dropped + + // Add more messages to test another recovery + SrsUniquePtr msg3(new SrsTsMessage()); + msg3->sid_ = SrsTsPESStreamIdVideoCommon; + msg3->dts_ = 180000; + msg3->pts_ = 180000; + msg3->ps_helper_ = &ps_ctx->helper_; + + err = ctx->on_ts_message(msg3.get()); + HELPER_EXPECT_SUCCESS(err); + + // Test: Third recovery with one message (nn_recover = 0, should increment) + ctx->on_recover_mode(0); + + // Verify: Recovery counter incremented (nn_recover <= 1), message dropped + EXPECT_EQ(2u, ctx->media_nn_recovered_); // Incremented to 2 + EXPECT_EQ(3u, ctx->media_nn_msgs_dropped_); // Total 3 messages dropped +} + +// Test SrsRecoverablePsContext::decode_rtp with valid RTP packet containing PS data +VOID TEST(RecoverablePsContextTest, DecodeRtpWithValidPacket) +{ + srs_error_t err; + + // Create mock handler + MockPsHandler handler; + + // Create context under test + SrsUniquePtr context(new SrsRecoverablePsContext()); + + // PT=DynamicRTP-Type-96, SSRC=0xBEBD135, Seq=31916, Time=95652000 + // This is a valid RTP packet containing PS data with audio PES packet + string raw = string( + "\x80\x60\x7c\xac\x05\xb3\x88\xa0\x0b\xeb\xd1\x35\x00\x00\x01\xc0" + "\x00\x6e\x8c\x80\x07\x25\x8a\x6d\xa9\xfd\xff\xf8\xff\xf9\x50\x40" + "\x0c\x9f\xfc\x01\x3a\x2e\x98\x28\x18\x0a\x09\x84\x81\x60\xc0\x50" + "\x2a\x12\x13\x05\x02\x22\x00\x88\x4c\x40\x11\x09\x85\x02\x61\x10" + "\xa8\x40\x00\x00\x00\x1f\xa6\x8d\xef\x03\xca\xf0\x63\x7f\x02\xe2" + "\x1d\x7f\xbf\x3e\x22\xbe\x3d\xf7\xa2\x7c\xba\xe6\xc8\xfb\x35\x9f" + "\xd1\xa2\xc4\xaa\xc5\x3d\xf6\x67\xfd\xc6\x39\x06\x9f\x9e\xdf\x9b" + "\x10\xd7\x4f\x59\xfd\xef\xea\xee\xc8\x4c\x40\xe5\xd9\xed\x00\x1c", + 128); + SrsUniquePtr buffer(new SrsBuffer((char *)raw.data(), raw.length())); + + // Decode RTP packet with no reserved bytes + HELPER_EXPECT_SUCCESS(context->decode_rtp(buffer.get(), 0, &handler)); + + // Verify successful decoding - should get one audio message + ASSERT_EQ((size_t)1, handler.msgs_.size()); + EXPECT_EQ(0, context->recover_); + + // Verify message properties + SrsTsMessage *msg = handler.msgs_.front(); + EXPECT_EQ(SrsTsPESStreamIdAudioCommon, msg->sid_); + EXPECT_EQ(100, msg->PES_packet_length_); + + // Verify RTP header fields were extracted correctly + EXPECT_EQ(31916, context->ctx_->helper()->rtp_seq_); + EXPECT_EQ(95652000, context->ctx_->helper()->rtp_ts_); + EXPECT_EQ(96, context->ctx_->helper()->rtp_pt_); +} + +// Test SrsRecoverablePsContext::enter_recover_mode with normal packet size +VOID TEST(RecoverablePsContextTest, EnterRecoverModeWithNormalPacket) +{ + srs_error_t err; + + // Create mock handler + MockPsHandler handler; + + // Create context under test + SrsUniquePtr context(new SrsRecoverablePsContext()); + + // Create a buffer with normal packet size (less than SRS_GB_LARGE_PACKET=1500) + char data[1000]; + memset(data, 0xFF, sizeof(data)); + SrsUniquePtr stream(new SrsBuffer(data, sizeof(data))); + + // Initialize helper with some test values + SrsPsDecodeHelper *h = context->ctx_->helper(); + h->rtp_seq_ = 12345; + h->rtp_ts_ = 90000; + h->rtp_pt_ = 96; + h->pack_first_seq_ = 100; + h->pack_nn_msgs_ = 5; + h->pack_pre_msg_last_seq_ = 99; + h->pack_id_ = 1; + + // Create a last message to be reaped + SrsTsMessage *last = context->ctx_->last(); + last->PES_packet_length_ = 200; + + // Simulate an error that triggers recovery + srs_error_t test_error = srs_error_new(ERROR_GB_PS_MEDIA, "test decode error"); + + // Record initial state + int initial_recover = context->recover_; + int initial_pos = stream->pos(); + + // Call enter_recover_mode + err = context->enter_recover_mode(stream.get(), &handler, initial_pos, test_error); + + // Verify: Should succeed (return srs_success) because packet size is normal + HELPER_EXPECT_SUCCESS(err); + + // Verify: Recovery counter incremented + EXPECT_EQ(initial_recover + 1, context->recover_); + + // Verify: Stream buffer fully consumed (all bytes skipped) + EXPECT_EQ(0, stream->left()); + + // Verify: Handler's on_recover_mode was called + // Note: MockPsHandler doesn't track this, but the method was invoked +} + +// Test SrsRecoverablePsContext recovery flow: enter recover mode, skip to pack header, quit recover mode +VOID TEST(RecoverablePsContextTest, RecoverModeSkipToPackHeaderAndQuit) +{ + // Create mock handler + MockPsHandler handler; + + // Create context under test + SrsUniquePtr context(new SrsRecoverablePsContext()); + + // Create a buffer with corrupted data followed by pack header (00 00 01 ba) + // Simulate real scenario: garbage bytes, then pack start code + char data[100]; + memset(data, 0xFF, sizeof(data)); // Fill with garbage + + // Place pack header at offset 50: 00 00 01 ba + data[50] = 0x00; + data[51] = 0x00; + data[52] = 0x01; + data[53] = 0xba; + + SrsUniquePtr stream(new SrsBuffer(data, sizeof(data))); + + // Initialize helper with test values + SrsPsDecodeHelper *h = context->ctx_->helper(); + h->rtp_seq_ = 54321; + h->rtp_ts_ = 180000; + h->rtp_pt_ = 96; + + // Simulate entering recover mode first + context->recover_ = 1; + + // Record initial position + int initial_pos = stream->pos(); + EXPECT_EQ(0, initial_pos); + + // Call srs_skip_util_pack to find pack header + bool found = srs_skip_util_pack(stream.get()); + + // Verify: Pack header was found + EXPECT_TRUE(found); + + // Verify: Stream position advanced to pack header (offset 50) + EXPECT_EQ(50, stream->pos()); + + // Verify: Still in recover mode + EXPECT_EQ(1, context->recover_); + + // Now call quit_recover_mode to exit recover mode + context->quit_recover_mode(stream.get(), &handler); + + // Verify: Exited recover mode (recover_ reset to 0) + EXPECT_EQ(0, context->recover_); + + // Verify: Stream position unchanged (still at pack header) + EXPECT_EQ(50, stream->pos()); + + // Verify: Remaining bytes available for decoding + EXPECT_EQ(50, stream->left()); +} + +// Mock ISrsHttpMessage implementation for GB publish API +MockHttpMessageForGbPublish::MockHttpMessageForGbPublish() +{ + mock_conn_ = new MockHttpConn(); + set_connection(mock_conn_); + body_content_ = ""; +} + +MockHttpMessageForGbPublish::~MockHttpMessageForGbPublish() +{ + srs_freep(mock_conn_); +} + +srs_error_t MockHttpMessageForGbPublish::body_read_all(std::string &body) +{ + body = body_content_; + return srs_success; +} + +// Mock ISrsResourceManager implementation for GB publish API +MockResourceManagerForGbPublish::MockResourceManagerForGbPublish() +{ +} + +MockResourceManagerForGbPublish::~MockResourceManagerForGbPublish() +{ +} + +srs_error_t MockResourceManagerForGbPublish::start() +{ + return srs_success; +} + +bool MockResourceManagerForGbPublish::empty() +{ + return id_map_.empty() && fast_id_map_.empty(); +} + +size_t MockResourceManagerForGbPublish::size() +{ + return id_map_.size(); +} + +void MockResourceManagerForGbPublish::add(ISrsResource *conn, bool *exists) +{ +} + +void MockResourceManagerForGbPublish::add_with_id(const std::string &id, ISrsResource *conn) +{ + id_map_[id] = conn; +} + +void MockResourceManagerForGbPublish::add_with_fast_id(uint64_t id, ISrsResource *conn) +{ + fast_id_map_[id] = conn; +} + +ISrsResource *MockResourceManagerForGbPublish::at(int index) +{ + return NULL; +} + +ISrsResource *MockResourceManagerForGbPublish::find_by_id(std::string id) +{ + if (id_map_.find(id) != id_map_.end()) { + return id_map_[id]; + } + return NULL; +} + +ISrsResource *MockResourceManagerForGbPublish::find_by_fast_id(uint64_t id) +{ + if (fast_id_map_.find(id) != fast_id_map_.end()) { + return fast_id_map_[id]; + } + return NULL; +} + +ISrsResource *MockResourceManagerForGbPublish::find_by_name(std::string /*name*/) +{ + return NULL; +} + +void MockResourceManagerForGbPublish::remove(ISrsResource *c) +{ +} + +void MockResourceManagerForGbPublish::subscribe(ISrsDisposingHandler *h) +{ +} + +void MockResourceManagerForGbPublish::unsubscribe(ISrsDisposingHandler *h) +{ +} + +void MockResourceManagerForGbPublish::reset() +{ + id_map_.clear(); + fast_id_map_.clear(); +} + +// Mock ISrsGbSession implementation for GB publish API +MockGbSessionForApiPublish::MockGbSessionForApiPublish() +{ + setup_called_ = false; + setup_owner_called_ = false; + setup_conf_ = NULL; + owner_coroutine_ = NULL; +} + +MockGbSessionForApiPublish::~MockGbSessionForApiPublish() +{ + owner_coroutine_ = NULL; +} + +void MockGbSessionForApiPublish::setup(SrsConfDirective *conf) +{ + setup_called_ = true; + setup_conf_ = conf; +} + +void MockGbSessionForApiPublish::setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid) +{ + setup_owner_called_ = true; + owner_coroutine_ = owner_coroutine; +} + +void MockGbSessionForApiPublish::on_media_transport(SrsSharedResource media) +{ +} + +void MockGbSessionForApiPublish::on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs) +{ +} + +const SrsContextId &MockGbSessionForApiPublish::get_id() +{ + static SrsContextId cid; + return cid; +} + +std::string MockGbSessionForApiPublish::desc() +{ + return "MockGbSessionForApiPublish"; +} + +srs_error_t MockGbSessionForApiPublish::cycle() +{ + srs_error_t err = srs_success; + + // Block like a real session would, until interrupted + while (true) { + if (!owner_coroutine_) { + return err; + } + if ((err = owner_coroutine_->pull()) != srs_success) { + return srs_error_wrap(err, "pull"); + } + + // Sleep to simulate work and allow interruption + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + } + + return err; +} + +void MockGbSessionForApiPublish::on_executor_done(ISrsInterruptable *executor) +{ + owner_coroutine_ = NULL; +} + +void MockGbSessionForApiPublish::reset() +{ + setup_called_ = false; + setup_owner_called_ = false; + setup_conf_ = NULL; + owner_coroutine_ = NULL; +} + +// Mock ISrsAppFactory implementation for GB publish API +MockAppFactoryForGbPublish::MockAppFactoryForGbPublish() +{ + mock_gb_session_ = new MockGbSessionForApiPublish(); +} + +MockAppFactoryForGbPublish::~MockAppFactoryForGbPublish() +{ + srs_freep(mock_gb_session_); +} + +ISrsFileWriter *MockAppFactoryForGbPublish::create_file_writer() +{ + return NULL; +} + +ISrsFileWriter *MockAppFactoryForGbPublish::create_enc_file_writer() +{ + return NULL; +} + +ISrsFileReader *MockAppFactoryForGbPublish::create_file_reader() +{ + return NULL; +} + +SrsPath *MockAppFactoryForGbPublish::create_path() +{ + return NULL; +} + +SrsLiveSource *MockAppFactoryForGbPublish::create_live_source() +{ + return NULL; +} + +ISrsOriginHub *MockAppFactoryForGbPublish::create_origin_hub() +{ + return NULL; +} + +ISrsHourGlass *MockAppFactoryForGbPublish::create_hourglass(const std::string &name, ISrsHourGlassHandler *handler, srs_utime_t interval) +{ + return NULL; +} + +ISrsBasicRtmpClient *MockAppFactoryForGbPublish::create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto) +{ + return NULL; +} + +SrsHttpClient *MockAppFactoryForGbPublish::create_http_client() +{ + return NULL; +} + +ISrsHttpResponseReader *MockAppFactoryForGbPublish::create_http_response_reader(ISrsHttpResponseReader *r) +{ + return NULL; +} + +ISrsFileReader *MockAppFactoryForGbPublish::create_http_file_reader(ISrsHttpResponseReader *r) +{ + return NULL; +} + +ISrsFlvDecoder *MockAppFactoryForGbPublish::create_flv_decoder() +{ + return NULL; +} + +ISrsBasicRtmpClient *MockAppFactoryForGbPublish::create_basic_rtmp_client(std::string url, srs_utime_t ctm, srs_utime_t stm) +{ + return NULL; +} + +#ifdef SRS_RTSP +ISrsRtspSendTrack *MockAppFactoryForGbPublish::create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) +{ + return NULL; +} + +ISrsRtspSendTrack *MockAppFactoryForGbPublish::create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) +{ + return NULL; +} +#endif + +ISrsFlvTransmuxer *MockAppFactoryForGbPublish::create_flv_transmuxer() +{ + return NULL; +} + +ISrsMp4Encoder *MockAppFactoryForGbPublish::create_mp4_encoder() +{ + return NULL; +} + +SrsDvrFlvSegmenter *MockAppFactoryForGbPublish::create_dvr_flv_segmenter() +{ + return NULL; +} + +SrsDvrMp4Segmenter *MockAppFactoryForGbPublish::create_dvr_mp4_segmenter() +{ + return NULL; +} + +ISrsGbMediaTcpConn *MockAppFactoryForGbPublish::create_gb_media_tcp_conn() +{ + return NULL; +} + +ISrsGbSession *MockAppFactoryForGbPublish::create_gb_session() +{ + // Return the mock session (ownership transferred to caller) + MockGbSessionForApiPublish *session = mock_gb_session_; + mock_gb_session_ = new MockGbSessionForApiPublish(); + return session; +} + +void MockAppFactoryForGbPublish::reset() +{ + srs_freep(mock_gb_session_); + mock_gb_session_ = new MockGbSessionForApiPublish(); +} + +// Test SrsGoApiGbPublish::do_serve_http - successful GB session creation +VOID TEST(GB28181Test, GoApiGbPublishSuccess) +{ + srs_error_t err; + + // Create mock dependencies + SrsUniquePtr mock_writer(new MockResponseWriter()); + SrsUniquePtr mock_request(new MockHttpMessageForGbPublish()); + SrsUniquePtr mock_manager(new MockResourceManagerForGbPublish()); + SrsUniquePtr mock_factory(new MockAppFactoryForGbPublish()); + SrsUniquePtr mock_config(new MockAppConfigForGbListener()); + + // Set up test configuration + SrsConfDirective *conf = new SrsConfDirective(); + conf->name_ = "stream_caster"; + mock_config->stream_caster_listen_port_ = 9000; + + // Create API handler + SrsUniquePtr api(new SrsGoApiGbPublish(conf)); + + // Inject mock dependencies + api->config_ = mock_config.get(); + api->gb_manager_ = mock_manager.get(); + api->app_factory_ = mock_factory.get(); + + // Set up request body with valid JSON + mock_request->body_content_ = "{\"id\":\"34020000001320000001\",\"ssrc\":\"1234567890\"}"; + + // Create response object + SrsUniquePtr res(SrsJsonAny::object()); + + // Call do_serve_http + err = api->do_serve_http(mock_writer.get(), mock_request.get(), res.get()); + HELPER_EXPECT_SUCCESS(err); + + // Verify response contains expected fields + SrsJsonAny *code = res->get_property("code"); + EXPECT_TRUE(code != NULL); + EXPECT_TRUE(code->is_integer()); + EXPECT_EQ(ERROR_SUCCESS, code->to_integer()); + + SrsJsonAny *port = res->get_property("port"); + EXPECT_TRUE(port != NULL); + EXPECT_TRUE(port->is_integer()); + EXPECT_EQ(9000, port->to_integer()); + + SrsJsonAny *is_tcp = res->get_property("is_tcp"); + EXPECT_TRUE(is_tcp != NULL); + EXPECT_TRUE(is_tcp->is_boolean()); + EXPECT_TRUE(is_tcp->to_boolean()); + + // Verify session was created and registered + ISrsResource *session_by_id = mock_manager->find_by_id("34020000001320000001"); + EXPECT_TRUE(session_by_id != NULL); + + ISrsResource *session_by_ssrc = mock_manager->find_by_fast_id(1234567890); + EXPECT_TRUE(session_by_ssrc != NULL); + + // Verify both lookups return the same session + EXPECT_EQ(session_by_id, session_by_ssrc); + + // Verify Connection header was set to Close + std::string connection_header = mock_writer->header()->get("Connection"); + EXPECT_STREQ("Close", connection_header.c_str()); + + // Clean up - interrupt and remove the created session/executor + // The session is wrapped in SrsSharedResource, and the executor manages it + SrsSharedResource *session_wrapper = dynamic_cast*>(session_by_id); + if (session_wrapper) { + ISrsGbSession *session = session_wrapper->get(); + MockGbSessionForApiPublish *mock_session = dynamic_cast(session); + if (mock_session && mock_session->owner_coroutine_) { + // Interrupt the executor coroutine to stop the cycle + mock_session->owner_coroutine_->interrupt(); + } + } + + // Wait a bit for the coroutine to finish + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + // Clean up - set injected fields to NULL to avoid double-free + api->config_ = NULL; + api->gb_manager_ = NULL; + api->app_factory_ = NULL; + + srs_freep(conf); +} + diff --git a/trunk/src/utest/srs_utest_app14.hpp b/trunk/src/utest/srs_utest_app14.hpp new file mode 100644 index 000000000..d9941c4c8 --- /dev/null +++ b/trunk/src/utest/srs_utest_app14.hpp @@ -0,0 +1,458 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_UTEST_APP14_HPP +#define SRS_UTEST_APP14_HPP + +/* +#include +*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SRS_RTSP +#include +#endif + +// Mock ISrsGbMuxer for testing SrsGbSession +class MockGbMuxer : public ISrsGbMuxer +{ +public: + bool setup_called_; + std::string setup_output_; + bool on_ts_message_called_; + srs_error_t on_ts_message_error_; + +public: + MockGbMuxer(); + virtual ~MockGbMuxer(); + +public: + virtual void setup(std::string output); + virtual srs_error_t on_ts_message(SrsTsMessage *msg); + void reset(); +}; + +// Mock ISrsAppConfig for testing SrsGbSession +class MockAppConfigForGbSession : public MockAppConfig +{ +public: + std::string stream_caster_output_; + +public: + MockAppConfigForGbSession(); + virtual ~MockAppConfigForGbSession(); + +public: + virtual std::string get_stream_caster_output(SrsConfDirective *conf); + void set_stream_caster_output(const std::string &output); +}; + +// Mock ISrsPackContext for testing SrsGbSession::on_ps_pack +class MockPackContext : public ISrsPackContext +{ +public: + MockPackContext(); + virtual ~MockPackContext(); + +public: + virtual srs_error_t on_ts_message(SrsTsMessage *msg); + virtual void on_recover_mode(int nn_recover); +}; + +// Mock ISrsGbMediaTcpConn for testing SrsGbSession::on_media_transport +class MockGbMediaTcpConn : public ISrsGbMediaTcpConn +{ +public: + bool set_cid_called_; + SrsContextId received_cid_; + bool is_connected_; + +public: + MockGbMediaTcpConn(); + virtual ~MockGbMediaTcpConn(); + +public: + virtual void setup(srs_netfd_t stfd); + virtual void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); + virtual bool is_connected(); + virtual void interrupt(); + virtual void set_cid(const SrsContextId &cid); + virtual const SrsContextId &get_id(); + virtual std::string desc(); + virtual srs_error_t cycle(); + virtual void on_executor_done(ISrsInterruptable *executor); + void reset(); +}; + +// Mock ISrsIpListener for testing SrsGbListener::initialize +class MockIpListener : public ISrsIpListener +{ +public: + std::string endpoint_ip_; + int endpoint_port_; + std::string label_; + bool set_endpoint_called_; + bool set_label_called_; + +public: + MockIpListener(); + virtual ~MockIpListener(); + +public: + virtual ISrsListener *set_endpoint(const std::string &i, int p); + virtual ISrsListener *set_label(const std::string &label); + virtual srs_error_t listen(); + void reset(); +}; + +// Mock ISrsAppConfig for testing SrsGbListener::initialize +class MockAppConfigForGbListener : public MockAppConfig +{ +public: + int stream_caster_listen_port_; + +public: + MockAppConfigForGbListener(); + virtual ~MockAppConfigForGbListener(); + +public: + virtual int get_stream_caster_listen(SrsConfDirective *conf); +}; + +// Mock ISrsHttpServeMux for testing SrsGbListener::listen_api +class MockHttpServeMuxForGbListener : public ISrsHttpServeMux +{ +public: + bool handle_called_; + std::string handle_pattern_; + ISrsHttpHandler *handle_handler_; + +public: + MockHttpServeMuxForGbListener(); + virtual ~MockHttpServeMuxForGbListener(); + +public: + virtual srs_error_t handle(std::string pattern, ISrsHttpHandler *handler); + virtual srs_error_t serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r); + void reset(); +}; + +// Mock ISrsApiServerOwner for testing SrsGbListener::listen_api +class MockApiServerOwnerForGbListener : public ISrsApiServerOwner +{ +public: + ISrsHttpServeMux *mux_; + +public: + MockApiServerOwnerForGbListener(); + virtual ~MockApiServerOwnerForGbListener(); + +public: + virtual ISrsHttpServeMux *api_server(); +}; + +// Mock ISrsIpListener for testing SrsGbListener::listen +class MockIpListenerForGbListen : public ISrsIpListener +{ +public: + bool listen_called_; + +public: + MockIpListenerForGbListen(); + virtual ~MockIpListenerForGbListen(); + +public: + virtual ISrsListener *set_endpoint(const std::string &i, int p); + virtual ISrsListener *set_label(const std::string &label); + virtual srs_error_t listen(); + void reset(); +}; + +// Mock ISrsGbSession for testing SrsGbMediaTcpConn::on_ps_pack +class MockGbSessionForMediaConn : public ISrsGbSession +{ +public: + bool on_ps_pack_called_; + ISrsPackContext *received_pack_; + SrsPsPacket *received_ps_; + std::vector received_msgs_; + bool on_media_transport_called_; + SrsSharedResource received_media_; + +public: + MockGbSessionForMediaConn(); + virtual ~MockGbSessionForMediaConn(); + +public: + virtual void setup(SrsConfDirective *conf); + virtual void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); + virtual void on_media_transport(SrsSharedResource media); + virtual void on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs); + virtual const SrsContextId &get_id(); + virtual std::string desc(); + virtual srs_error_t cycle(); + virtual void on_executor_done(ISrsInterruptable *executor); + void reset(); +}; + +// Mock ISrsResourceManager for testing SrsGbMediaTcpConn::bind_session +class MockResourceManagerForBindSession : public ISrsResourceManager +{ +public: + ISrsResource *session_to_return_; + +public: + MockResourceManagerForBindSession(); + virtual ~MockResourceManagerForBindSession(); + +public: + virtual srs_error_t start(); + virtual bool empty(); + virtual size_t size(); + virtual void add(ISrsResource *conn, bool *exists = NULL); + virtual void add_with_id(const std::string &id, ISrsResource *conn); + virtual void add_with_fast_id(uint64_t id, ISrsResource *conn); + virtual ISrsResource *at(int index); + virtual ISrsResource *find_by_id(std::string id); + virtual ISrsResource *find_by_fast_id(uint64_t id); + virtual ISrsResource *find_by_name(std::string name); + virtual void remove(ISrsResource *c); + virtual void subscribe(ISrsDisposingHandler *h); + virtual void unsubscribe(ISrsDisposingHandler *h); + void reset(); +}; + +// Mock ISrsBasicRtmpClient for testing SrsGbMuxer +class MockGbRtmpClient : public ISrsBasicRtmpClient +{ +public: + bool connect_called_; + bool publish_called_; + bool close_called_; + srs_error_t connect_error_; + srs_error_t publish_error_; + int stream_id_; + +public: + MockGbRtmpClient(); + virtual ~MockGbRtmpClient(); + +public: + virtual srs_error_t connect(); + virtual void close(); + virtual srs_error_t publish(int chunk_size, bool with_vhost = true, std::string *pstream = NULL); + virtual srs_error_t play(int chunk_size, bool with_vhost = true, std::string *pstream = NULL); + virtual void kbps_sample(const char *label, srs_utime_t age); + virtual int sid(); + virtual srs_error_t recv_message(SrsRtmpCommonMessage **pmsg); + virtual srs_error_t decode_message(SrsRtmpCommonMessage *msg, SrsRtmpCommand **ppacket); + virtual srs_error_t send_and_free_messages(SrsMediaPacket **msgs, int nb_msgs); + virtual srs_error_t send_and_free_message(SrsMediaPacket *msg); + virtual void set_recv_timeout(srs_utime_t timeout); + void reset(); +}; + +// Mock ISrsRawAacStream for testing SrsGbMuxer +class MockGbRawAacStream : public ISrsRawAacStream +{ +public: + bool adts_demux_called_; + bool mux_sequence_header_called_; + bool mux_aac2flv_called_; + srs_error_t adts_demux_error_; + srs_error_t mux_sequence_header_error_; + srs_error_t mux_aac2flv_error_; + std::string sequence_header_output_; + int demux_frame_size_; + +public: + MockGbRawAacStream(); + virtual ~MockGbRawAacStream(); + +public: + virtual srs_error_t adts_demux(SrsBuffer *stream, char **pframe, int *pnb_frame, SrsRawAacStreamCodec &codec); + virtual srs_error_t mux_sequence_header(SrsRawAacStreamCodec *codec, std::string &sh); + virtual srs_error_t mux_aac2flv(char *frame, int nb_frame, SrsRawAacStreamCodec *codec, uint32_t dts, char **flv, int *nb_flv); + void reset(); +}; + +// Mock ISrsMpegpsQueue for testing SrsGbMuxer +class MockGbMpegpsQueue : public ISrsMpegpsQueue +{ +public: + bool push_called_; + srs_error_t push_error_; + int push_count_; + +public: + MockGbMpegpsQueue(); + virtual ~MockGbMpegpsQueue(); + +public: + virtual srs_error_t push(SrsMediaPacket *msg); + virtual SrsMediaPacket *dequeue(); + void reset(); +}; + +// Mock ISrsGbSession for testing SrsGbMuxer +class MockGbSessionForMuxer : public ISrsGbSession +{ +public: + std::string device_id_; + +public: + MockGbSessionForMuxer(); + virtual ~MockGbSessionForMuxer(); + +public: + virtual void setup(SrsConfDirective *conf); + virtual void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); + virtual void on_media_transport(SrsSharedResource media); + virtual void on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs); + virtual const SrsContextId &get_id(); + virtual std::string desc(); + virtual srs_error_t cycle(); + virtual void on_executor_done(ISrsInterruptable *executor); +}; + +// Mock ISrsPsPackHandler for testing SrsPackContext +class MockPsPackHandler : public ISrsPsPackHandler +{ +public: + bool on_ps_pack_called_; + int on_ps_pack_count_; + uint32_t last_pack_id_; + int last_msgs_count_; + srs_error_t on_ps_pack_error_; + +public: + MockPsPackHandler(); + virtual ~MockPsPackHandler(); + +public: + virtual srs_error_t on_ps_pack(SrsPsPacket *ps, const std::vector &msgs); + void reset(); +}; + +// Mock ISrsHttpMessage for testing SrsGoApiGbPublish +class MockHttpMessageForGbPublish : public SrsHttpMessage +{ +public: + std::string body_content_; + MockHttpConn *mock_conn_; + +public: + MockHttpMessageForGbPublish(); + virtual ~MockHttpMessageForGbPublish(); + +public: + virtual srs_error_t body_read_all(std::string &body); +}; + +// Mock ISrsResourceManager for testing SrsGoApiGbPublish +class MockResourceManagerForGbPublish : public ISrsResourceManager +{ +public: + std::map id_map_; + std::map fast_id_map_; + +public: + MockResourceManagerForGbPublish(); + virtual ~MockResourceManagerForGbPublish(); + +public: + virtual srs_error_t start(); + virtual bool empty(); + virtual size_t size(); + virtual void add(ISrsResource *conn, bool *exists = NULL); + virtual void add_with_id(const std::string &id, ISrsResource *conn); + virtual void add_with_fast_id(uint64_t id, ISrsResource *conn); + virtual ISrsResource *at(int index); + virtual ISrsResource *find_by_id(std::string id); + virtual ISrsResource *find_by_fast_id(uint64_t id); + virtual ISrsResource *find_by_name(std::string name); + virtual void remove(ISrsResource *c); + virtual void subscribe(ISrsDisposingHandler *h); + virtual void unsubscribe(ISrsDisposingHandler *h); + void reset(); +}; + +// Mock ISrsGbSession for testing SrsGoApiGbPublish +class MockGbSessionForApiPublish : public ISrsGbSession +{ +public: + bool setup_called_; + bool setup_owner_called_; + SrsConfDirective *setup_conf_; + ISrsInterruptable *owner_coroutine_; + +public: + MockGbSessionForApiPublish(); + virtual ~MockGbSessionForApiPublish(); + +public: + virtual void setup(SrsConfDirective *conf); + virtual void setup_owner(SrsSharedResource *wrapper, ISrsInterruptable *owner_coroutine, ISrsContextIdSetter *owner_cid); + virtual void on_media_transport(SrsSharedResource media); + virtual void on_ps_pack(ISrsPackContext *ctx, SrsPsPacket *ps, const std::vector &msgs); + virtual const SrsContextId &get_id(); + virtual std::string desc(); + virtual srs_error_t cycle(); + virtual void on_executor_done(ISrsInterruptable *executor); + void reset(); +}; + +// Mock ISrsAppFactory for testing SrsGoApiGbPublish +class MockAppFactoryForGbPublish : public ISrsAppFactory +{ +public: + MockGbSessionForApiPublish *mock_gb_session_; + +public: + MockAppFactoryForGbPublish(); + virtual ~MockAppFactoryForGbPublish(); + +public: + virtual ISrsFileWriter *create_file_writer(); + virtual ISrsFileWriter *create_enc_file_writer(); + virtual ISrsFileReader *create_file_reader(); + virtual SrsPath *create_path(); + virtual SrsLiveSource *create_live_source(); + virtual ISrsOriginHub *create_origin_hub(); + virtual ISrsHourGlass *create_hourglass(const std::string &name, ISrsHourGlassHandler *handler, srs_utime_t interval); + virtual ISrsBasicRtmpClient *create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto); + virtual SrsHttpClient *create_http_client(); + virtual ISrsHttpResponseReader *create_http_response_reader(ISrsHttpResponseReader *r); + virtual ISrsFileReader *create_http_file_reader(ISrsHttpResponseReader *r); + virtual ISrsFlvDecoder *create_flv_decoder(); + virtual ISrsBasicRtmpClient *create_basic_rtmp_client(std::string url, srs_utime_t ctm, srs_utime_t stm); +#ifdef SRS_RTSP + virtual ISrsRtspSendTrack *create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc); + virtual ISrsRtspSendTrack *create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc); +#endif + virtual ISrsFlvTransmuxer *create_flv_transmuxer(); + virtual ISrsMp4Encoder *create_mp4_encoder(); + virtual SrsDvrFlvSegmenter *create_dvr_flv_segmenter(); + virtual SrsDvrMp4Segmenter *create_dvr_mp4_segmenter(); + virtual ISrsGbMediaTcpConn *create_gb_media_tcp_conn(); + virtual ISrsGbSession *create_gb_session(); + void reset(); +}; + +#endif + diff --git a/trunk/src/utest/srs_utest_app6.cpp b/trunk/src/utest/srs_utest_app6.cpp index 123ad2396..ce09aeb20 100644 --- a/trunk/src/utest/srs_utest_app6.cpp +++ b/trunk/src/utest/srs_utest_app6.cpp @@ -166,6 +166,15 @@ void MockRtcNetwork::reset() is_established_ = true; } +srs_error_t MockRtcNetwork::initialize(SrsSessionConfig *cfg, bool dtls, bool srtp) +{ + return srs_success; +} + +void MockRtcNetwork::set_state(SrsRtcNetworkState state) +{ +} + srs_error_t MockRtcNetwork::on_dtls_handshake_done() { on_dtls_handshake_done_count_++; @@ -180,6 +189,11 @@ srs_error_t MockRtcNetwork::on_dtls_alert(std::string type, std::string desc) return srs_error_copy(on_dtls_alert_error_); } +srs_error_t MockRtcNetwork::on_dtls(char *data, int nb_data) +{ + return srs_success; +} + srs_error_t MockRtcNetwork::protect_rtp(void *packet, int *nb_cipher) { protect_rtp_count_++; @@ -192,6 +206,21 @@ srs_error_t MockRtcNetwork::protect_rtcp(void *packet, int *nb_cipher) return srs_error_copy(protect_rtcp_error_); } +srs_error_t MockRtcNetwork::on_stun(SrsStunPacket *r, char *data, int nb_data) +{ + return srs_success; +} + +srs_error_t MockRtcNetwork::on_rtp(char *data, int nb_data) +{ + return srs_success; +} + +srs_error_t MockRtcNetwork::on_rtcp(char *data, int nb_data) +{ + return srs_success; +} + bool MockRtcNetwork::is_establelished() { return is_established_; diff --git a/trunk/src/utest/srs_utest_app6.hpp b/trunk/src/utest/srs_utest_app6.hpp index 3f37b394a..da8c16635 100644 --- a/trunk/src/utest/srs_utest_app6.hpp +++ b/trunk/src/utest/srs_utest_app6.hpp @@ -84,10 +84,16 @@ public: virtual ~MockRtcNetwork(); public: + virtual srs_error_t initialize(SrsSessionConfig *cfg, bool dtls, bool srtp); + virtual void set_state(SrsRtcNetworkState state); virtual srs_error_t on_dtls_handshake_done(); virtual srs_error_t on_dtls_alert(std::string type, std::string desc); + virtual srs_error_t on_dtls(char *data, int nb_data); virtual srs_error_t protect_rtp(void *packet, int *nb_cipher); virtual srs_error_t protect_rtcp(void *packet, int *nb_cipher); + virtual srs_error_t on_stun(SrsStunPacket *r, char *data, int nb_data); + virtual srs_error_t on_rtp(char *data, int nb_data); + virtual srs_error_t on_rtcp(char *data, int nb_data); virtual bool is_establelished(); virtual srs_error_t write(void *buf, size_t size, ssize_t *nwrite); @@ -291,6 +297,8 @@ public: virtual std::vector get_stream_casters() { return std::vector(); } virtual bool get_stream_caster_enabled(SrsConfDirective *conf) { return false; } virtual std::string get_stream_caster_engine(SrsConfDirective *conf) { return ""; } + virtual std::string get_stream_caster_output(SrsConfDirective *conf) { return ""; } + virtual int get_stream_caster_listen(SrsConfDirective *conf) { return 0; } virtual bool get_exporter_enabled() { return false; } virtual std::string get_exporter_listen() { return ""; } virtual std::string get_exporter_label() { return ""; } diff --git a/trunk/src/utest/srs_utest_app7.cpp b/trunk/src/utest/srs_utest_app7.cpp index 8ed2d2d85..28b3e101f 100644 --- a/trunk/src/utest/srs_utest_app7.cpp +++ b/trunk/src/utest/srs_utest_app7.cpp @@ -1021,11 +1021,34 @@ void MockConnectionManagerForExpire::add(ISrsResource * /*conn*/, bool * /*exist { } +void MockConnectionManagerForExpire::add_with_id(const std::string & /*id*/, ISrsResource * /*conn*/) +{ +} + +void MockConnectionManagerForExpire::add_with_fast_id(uint64_t /*id*/, ISrsResource * /*conn*/) +{ +} + ISrsResource *MockConnectionManagerForExpire::at(int /*index*/) { return NULL; } +ISrsResource *MockConnectionManagerForExpire::find_by_id(std::string /*id*/) +{ + return NULL; +} + +ISrsResource *MockConnectionManagerForExpire::find_by_fast_id(uint64_t /*id*/) +{ + return NULL; +} + +ISrsResource *MockConnectionManagerForExpire::find_by_name(std::string /*name*/) +{ + return NULL; +} + void MockConnectionManagerForExpire::remove(ISrsResource *c) { removed_resource_ = c; diff --git a/trunk/src/utest/srs_utest_app7.hpp b/trunk/src/utest/srs_utest_app7.hpp index fd18bfa86..88e0e9960 100644 --- a/trunk/src/utest/srs_utest_app7.hpp +++ b/trunk/src/utest/srs_utest_app7.hpp @@ -105,7 +105,12 @@ public: virtual bool empty(); virtual size_t size(); virtual void add(ISrsResource *conn, bool *exists = NULL); + virtual void add_with_id(const std::string &id, ISrsResource *conn); + virtual void add_with_fast_id(uint64_t id, ISrsResource *conn); virtual ISrsResource *at(int index); + virtual ISrsResource *find_by_id(std::string id); + virtual ISrsResource *find_by_fast_id(uint64_t id); + virtual ISrsResource *find_by_name(std::string name); virtual void remove(ISrsResource *c); virtual void subscribe(ISrsDisposingHandler *h); virtual void unsubscribe(ISrsDisposingHandler *h); diff --git a/trunk/src/utest/srs_utest_gb28181.cpp b/trunk/src/utest/srs_utest_gb28181.cpp index a474e9c0d..8ef0817e4 100644 --- a/trunk/src/utest/srs_utest_gb28181.cpp +++ b/trunk/src/utest/srs_utest_gb28181.cpp @@ -47,7 +47,7 @@ VOID TEST(KernelPSTest, PsPacketDecodePartialPesHeader2) SrsRecoverablePsContext context; // Ignore if PS header is not integrity. - context.ctx_.set_detect_ps_integrity(true); + context.ctx_->set_detect_ps_integrity(true); // A PES packet with complete header, but without enough data. string raw = string("\x00\x00\x01\xc0\x00\x82\x8c\x80", 8); @@ -445,7 +445,7 @@ VOID TEST(KernelPSTest, PsPacketDecodeInvalidStartCode) ASSERT_EQ((size_t)3, handler.msgs_.size()); EXPECT_EQ(0, context.recover_); - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(65472, last->PES_packet_length_); ASSERT_EQ(1156, last->payload_->length()); } @@ -459,12 +459,12 @@ VOID TEST(KernelPSTest, PsPacketDecodeInvalidStartCode) ASSERT_EQ((size_t)3, handler.msgs_.size()); // We don't clear handler, so there must be 3 messages. EXPECT_EQ(0, context.recover_); - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(65472, last->PES_packet_length_); ASSERT_EQ(1156 + 1400 * (i + 1), last->payload_->length()); } if (true) { - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(65472, last->PES_packet_length_); ASSERT_EQ(64156, last->payload_->length()); } @@ -487,7 +487,7 @@ VOID TEST(KernelPSTest, PsPacketDecodeInvalidStartCode) ASSERT_EQ((size_t)4, handler.msgs_.size()); EXPECT_EQ(0, context.recover_); - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(65472, last->PES_packet_length_); ASSERT_EQ(72, last->payload_->length()); } @@ -501,12 +501,12 @@ VOID TEST(KernelPSTest, PsPacketDecodeInvalidStartCode) ASSERT_EQ((size_t)4, handler.msgs_.size()); // We don't clear handler, so there must be 4 messages. EXPECT_EQ(0, context.recover_); - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(65472, last->PES_packet_length_); ASSERT_EQ(72 + 1400 * (i + 1), last->payload_->length()); } if (true) { - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(65472, last->PES_packet_length_); ASSERT_EQ(64472, last->payload_->length()); } @@ -526,7 +526,7 @@ VOID TEST(KernelPSTest, PsPacketDecodeInvalidStartCode) ASSERT_EQ((size_t)5, handler.msgs_.size()); EXPECT_EQ(0, context.recover_); - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(10172, last->PES_packet_length_); ASSERT_EQ(388, last->payload_->length()); } @@ -540,12 +540,12 @@ VOID TEST(KernelPSTest, PsPacketDecodeInvalidStartCode) ASSERT_EQ((size_t)5, handler.msgs_.size()); // We don't clear handler, so there must be 5 messages. EXPECT_EQ(0, context.recover_); - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(10172, last->PES_packet_length_); ASSERT_EQ(388 + 1400 * (i + 1), last->payload_->length()); } if (true) { - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(10172, last->PES_packet_length_); ASSERT_EQ(8788, last->payload_->length()); } @@ -564,7 +564,7 @@ VOID TEST(KernelPSTest, PsPacketDecodeInvalidStartCode) ASSERT_EQ((size_t)6, handler.msgs_.size()); EXPECT_EQ(0, context.recover_); - SrsTsMessage *last = context.ctx_.last_; + SrsTsMessage *last = context.ctx_->last(); ASSERT_EQ(96, last->PES_packet_length_); ASSERT_EQ(0, last->payload_->length()); } diff --git a/trunk/src/utest/srs_utest_service.cpp b/trunk/src/utest/srs_utest_service.cpp index 5eed8ccf6..3f8e3ead9 100644 --- a/trunk/src/utest/srs_utest_service.cpp +++ b/trunk/src/utest/srs_utest_service.cpp @@ -1371,11 +1371,34 @@ void MockConnectionManager::add(ISrsResource * /*conn*/, bool * /*exists*/) { } +void MockConnectionManager::add_with_id(const std::string & /*id*/, ISrsResource * /*conn*/) +{ +} + +void MockConnectionManager::add_with_fast_id(uint64_t /*id*/, ISrsResource * /*conn*/) +{ +} + ISrsResource *MockConnectionManager::at(int /*index*/) { return NULL; } +ISrsResource *MockConnectionManager::find_by_id(std::string /*id*/) +{ + return NULL; +} + +ISrsResource *MockConnectionManager::find_by_fast_id(uint64_t /*id*/) +{ + return NULL; +} + +ISrsResource *MockConnectionManager::find_by_name(std::string /*name*/) +{ + return NULL; +} + void MockConnectionManager::remove(ISrsResource * /*c*/) { } diff --git a/trunk/src/utest/srs_utest_service.hpp b/trunk/src/utest/srs_utest_service.hpp index e79b1cdff..562f7017e 100644 --- a/trunk/src/utest/srs_utest_service.hpp +++ b/trunk/src/utest/srs_utest_service.hpp @@ -92,7 +92,12 @@ public: virtual bool empty(); virtual size_t size(); virtual void add(ISrsResource *conn, bool *exists = NULL); + virtual void add_with_id(const std::string &id, ISrsResource *conn); + virtual void add_with_fast_id(uint64_t id, ISrsResource *conn); virtual ISrsResource *at(int index); + virtual ISrsResource *find_by_id(std::string id); + virtual ISrsResource *find_by_fast_id(uint64_t id); + virtual ISrsResource *find_by_name(std::string name); virtual void remove(ISrsResource *c); virtual void subscribe(ISrsDisposingHandler *h); virtual void unsubscribe(ISrsDisposingHandler *h);