diff --git a/.augment-guidelines b/.augment-guidelines index a11cd8b47..a75951afb 100644 --- a/.augment-guidelines +++ b/.augment-guidelines @@ -579,6 +579,66 @@ testing: - Verify edge cases like sequence number wrap-around, cache overflow, and null inputs - Use existing mock helper functions for consistency and maintainability + mock_interface_fields: + principle: "MANDATORY - Always mock all private/protected interface member fields (ISrs* types) in the class under test" + description: | + When writing unit tests, ALWAYS identify and mock ALL interface member fields in the class under test. + Interface fields are those with ISrs* prefix (e.g., ISrsAppConfig*, ISrsBasicRtmpClient*, ISrsRequest*). + This enables proper dependency injection and isolation of the unit under test. + + process: + - "Step 1: View the class header file to identify all private/protected member fields" + - "Step 2: Identify which fields are interfaces (start with ISrs prefix)" + - "Step 3: Create or reuse mock classes for each interface type" + - "Step 4: In the test, inject mock instances into the class under test by setting the private fields directly" + - "Step 5: After test completes, set injected fields to NULL before object destruction to avoid double-free" + + example: | + // Class under test has these private fields: + class SrsEdgeRtmpUpstream { + private: + ISrsAppConfig *config_; // Interface - MUST mock + ISrsBasicRtmpClient *sdk_; // Interface - MUST mock + std::string redirect_; // Not interface - no need to mock + int selected_port_; // Not interface - no need to mock + }; + + // In unit test: + VOID TEST(EdgeRtmpUpstreamTest, ConnectToOrigin) { + // Create mocks for ALL interface fields + SrsUniquePtr mock_config(new MockEdgeConfig()); + MockEdgeRtmpClient *mock_sdk = new MockEdgeRtmpClient(); + + // Create object under test + SrsUniquePtr upstream(new SrsEdgeRtmpUpstream("")); + + // Inject mocks into private interface fields + upstream->config_ = mock_config.get(); + upstream->sdk_ = mock_sdk; + + // Run test + err = upstream->connect(req.get(), lb.get()); + HELPER_EXPECT_SUCCESS(err); + + // Verify mock interactions + EXPECT_TRUE(mock_sdk->connect_called_); + + // Clean up - set to NULL to avoid double-free + upstream->sdk_ = NULL; + srs_freep(mock_sdk); + } + + rules: + - "ALWAYS view the class header file first to identify all member fields" + - "ALWAYS mock ALL interface fields (ISrs* prefix) - no exceptions" + - "Create mock classes that implement the interface if they don't exist" + - "Reuse existing mock classes from srs_utest_app*.hpp when available" + - "Inject mocks by directly setting private member fields (accessible in utests)" + - "Set injected fields to NULL before object destruction to prevent double-free" + - "Non-interface fields (std::string, int, etc.) do not need mocking" + + rationale: "Mocking all interface dependencies ensures tests are isolated, fast, and don't require real network/file/database resources. It also makes tests deterministic and easier to debug." + test_object_declaration: - pattern: "Use unique pointers for object instantiation" description: "MANDATORY - Always use SrsUniquePtr for object declaration in unit tests instead of stack allocation" diff --git a/trunk/auto/utest.sh b/trunk/auto/utest.sh index b732aa594..835ea9c48 100755 --- a/trunk/auto/utest.sh +++ b/trunk/auto/utest.sh @@ -138,14 +138,23 @@ echo "" >> ${FILE}; echo "" >> ${FILE} # Depends, the depends objects echo "# Depends, the depends objects" >> ${FILE} # -# current module header files -echo -n "SRS_UTEST_DEPS = " >> ${FILE} +# current SRS object files +echo -n "SRS_SOURCE_OBJS = " >> ${FILE} for item in ${MODULE_OBJS[*]}; do FILE_NAME=${item%.*} echo -n "${SRS_TRUNK_PREFIX}/${SRS_OBJS}/${FILE_NAME}.o " >> ${FILE} done echo "" >> ${FILE}; echo "" >> ${FILE} # +# current SRS header files +echo -n "SRS_SOURCE_HEADERS = " >> ${FILE} +for item in ${MODULE_OBJS[*]}; do + FILE_NAME=${item%.*} + echo -n "${SRS_TRUNK_PREFIX}/${FILE_NAME}.hpp " >> ${FILE} +done +echo "" >> ${FILE}; echo "" >> ${FILE} +# +# current utest header files echo "# Depends, utest header files" >> ${FILE} DEPS_NAME="UTEST_DEPS" echo -n "${DEPS_NAME} = " >> ${FILE} @@ -163,7 +172,7 @@ MODULE_OBJS=() for item in ${MODULE_FILES[*]}; do MODULE_OBJS="${MODULE_OBJS[@]} ${item}.o" cat << END >> ${FILE} -${item}.o : \$(${DEPS_NAME}) ${SRS_TRUNK_PREFIX}/${MODULE_DIR}/${item}.cpp \$(SRS_UTEST_DEPS) +${item}.o : \$(${DEPS_NAME}) ${SRS_TRUNK_PREFIX}/${MODULE_DIR}/${item}.cpp \$(SRS_SOURCE_HEADERS) \$(CXX) \$(CPPFLAGS) \$(CXXFLAGS) \$(SRS_UTEST_INC) -c ${SRS_TRUNK_PREFIX}/${MODULE_DIR}/${item}.cpp -o \$@ END done @@ -186,7 +195,7 @@ echo "" >> ${FILE}; echo "" >> ${FILE} # echo "# generate the utest binary" >> ${FILE} cat << END >> ${FILE} -${SRS_TRUNK_PREFIX}/${SRS_OBJS}/${APP_NAME} : \$(SRS_UTEST_DEPS) ${MODULE_OBJS} gtest.a +${SRS_TRUNK_PREFIX}/${SRS_OBJS}/${APP_NAME} : \$(SRS_SOURCE_OBJS) ${MODULE_OBJS} gtest.a \$(CXX) -o \$@ \$(CPPFLAGS) \$(CXXFLAGS) \$^ \$(DEPS_LIBRARIES_FILES) ${LINK_OPTIONS} END diff --git a/trunk/configure b/trunk/configure index 93e5d4507..3a0c67e6f 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_app10" "srs_utest_app11" "srs_utest_app12" "srs_utest_app13") # Always include SRT utest MODULE_FILES+=("srs_utest_srt") if [[ $SRS_GB28181 == YES ]]; then diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 7c9517879..f63bd12ea 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -504,6 +504,11 @@ public: virtual bool get_vhost_http_remux_has_video(std::string vhost) = 0; virtual bool get_vhost_http_remux_guess_has_av(std::string vhost) = 0; virtual std::string get_vhost_http_remux_mount(std::string vhost) = 0; + +public: + virtual std::string get_vhost_edge_protocol(std::string vhost) = 0; + virtual bool get_vhost_edge_follow_client(std::string vhost) = 0; + virtual std::string get_vhost_edge_transform_vhost(std::string vhost) = 0; }; // The config service provider. diff --git a/trunk/src/app/srs_app_edge.cpp b/trunk/src/app/srs_app_edge.cpp index 1c393ec2a..89348b041 100644 --- a/trunk/src/app/srs_app_edge.cpp +++ b/trunk/src/app/srs_app_edge.cpp @@ -21,6 +21,7 @@ using namespace std; #include #include +#include #include #include #include @@ -42,11 +43,11 @@ using namespace std; // when edge error, wait for quit #define SRS_EDGE_FORWARDER_TIMEOUT (150 * SRS_UTIME_MILLISECONDS) -SrsEdgeUpstream::SrsEdgeUpstream() +ISrsEdgeUpstream::ISrsEdgeUpstream() { } -SrsEdgeUpstream::~SrsEdgeUpstream() +ISrsEdgeUpstream::~ISrsEdgeUpstream() { } @@ -55,11 +56,17 @@ SrsEdgeRtmpUpstream::SrsEdgeRtmpUpstream(string r) redirect_ = r; sdk_ = NULL; selected_port_ = 0; + + config_ = _srs_config; + app_factory_ = _srs_app_factory; } SrsEdgeRtmpUpstream::~SrsEdgeRtmpUpstream() { close(); + + config_ = NULL; + app_factory_ = NULL; } srs_error_t SrsEdgeRtmpUpstream::connect(ISrsRequest *r, ISrsLbRoundRobin *lb) @@ -70,7 +77,7 @@ srs_error_t SrsEdgeRtmpUpstream::connect(ISrsRequest *r, ISrsLbRoundRobin *lb) std::string url; if (true) { - SrsConfDirective *conf = _srs_config->get_vhost_edge_origin(req->vhost_); + SrsConfDirective *conf = config_->get_vhost_edge_origin(req->vhost_); // when origin is error, for instance, server is shutdown, // then user remove the vhost then reload, the conf is empty. @@ -95,7 +102,7 @@ srs_error_t SrsEdgeRtmpUpstream::connect(ISrsRequest *r, ISrsLbRoundRobin *lb) selected_port_ = port; // support vhost tranform for edge, - std::string vhost = _srs_config->get_vhost_edge_transform_vhost(req->vhost_); + std::string vhost = config_->get_vhost_edge_transform_vhost(req->vhost_); vhost = srs_strings_replace(vhost, "[vhost]", req->vhost_); url = srs_net_url_encode_rtmp_url(server, port, req->host_, vhost, req->app_, req->stream_, req->param_); @@ -104,7 +111,8 @@ srs_error_t SrsEdgeRtmpUpstream::connect(ISrsRequest *r, ISrsLbRoundRobin *lb) srs_freep(sdk_); srs_utime_t cto = SRS_EDGE_INGESTER_TIMEOUT; srs_utime_t sto = SRS_CONSTS_RTMP_PULSE; - sdk_ = new SrsSimpleRtmpClient(url, cto, sto); + // Use factory to create client. + sdk_ = app_factory_->create_rtmp_client(url, cto, sto); if ((err = sdk_->connect()) != srs_success) { return srs_error_wrap(err, "edge pull %s failed, cto=%dms, sto=%dms.", url.c_str(), srsu2msi(cto), srsu2msi(sto)); @@ -113,7 +121,7 @@ srs_error_t SrsEdgeRtmpUpstream::connect(ISrsRequest *r, ISrsLbRoundRobin *lb) // For RTMP client, we pass the vhost in tcUrl when connecting, // so we publish without vhost in stream. string stream; - if ((err = sdk_->play(_srs_config->get_chunk_size(req->vhost_), false, &stream)) != srs_success) { + if ((err = sdk_->play(config_->get_chunk_size(req->vhost_), false, &stream)) != srs_success) { return srs_error_wrap(err, "edge pull %s stream failed", url.c_str()); } @@ -163,11 +171,17 @@ SrsEdgeFlvUpstream::SrsEdgeFlvUpstream(std::string schema) reader_ = NULL; decoder_ = NULL; req_ = NULL; + + config_ = _srs_config; + app_factory_ = _srs_app_factory; } SrsEdgeFlvUpstream::~SrsEdgeFlvUpstream() { close(); + + config_ = NULL; + app_factory_ = NULL; } srs_error_t SrsEdgeFlvUpstream::connect(ISrsRequest *r, ISrsLbRoundRobin *lb) @@ -189,7 +203,7 @@ srs_error_t SrsEdgeFlvUpstream::do_connect(ISrsRequest *r, ISrsLbRoundRobin *lb, ISrsRequest *req = r; if (redirect_depth == 0) { - SrsConfDirective *conf = _srs_config->get_vhost_edge_origin(req->vhost_); + SrsConfDirective *conf = config_->get_vhost_edge_origin(req->vhost_); // when origin is error, for instance, server is shutdown, // then user remove the vhost then reload, the conf is empty. @@ -216,7 +230,7 @@ srs_error_t SrsEdgeFlvUpstream::do_connect(ISrsRequest *r, ISrsLbRoundRobin *lb, } srs_freep(sdk_); - sdk_ = new SrsHttpClient(); + sdk_ = app_factory_->create_http_client(); string path = "/" + req->app_ + "/" + req->stream_; if (!srs_strings_ends_with(req->stream_, ".flv")) { @@ -275,10 +289,10 @@ srs_error_t SrsEdgeFlvUpstream::do_connect(ISrsRequest *r, ISrsLbRoundRobin *lb, } srs_freep(reader_); - reader_ = new SrsHttpFileReader(hr_->body_reader()); + reader_ = app_factory_->create_http_file_reader(hr_->body_reader()); srs_freep(decoder_); - decoder_ = new SrsFlvDecoder(); + decoder_ = app_factory_->create_flv_decoder(); if ((err = decoder_->initialize(reader_)) != srs_success) { return srs_error_wrap(err, "init decoder"); @@ -383,6 +397,14 @@ void SrsEdgeFlvUpstream::kbps_sample(const char *label, srs_utime_t age) sdk_->kbps_sample(label, age); } +ISrsEdgeIngester::ISrsEdgeIngester() +{ +} + +ISrsEdgeIngester::~ISrsEdgeIngester() +{ +} + SrsEdgeIngester::SrsEdgeIngester() { source_ = NULL; @@ -392,6 +414,8 @@ SrsEdgeIngester::SrsEdgeIngester() upstream_ = new SrsEdgeRtmpUpstream(""); lb_ = new SrsLbRoundRobin(); trd_ = new SrsDummyCoroutine(); + + config_ = _srs_config; } SrsEdgeIngester::~SrsEdgeIngester() @@ -401,6 +425,8 @@ SrsEdgeIngester::~SrsEdgeIngester() srs_freep(upstream_); srs_freep(lb_); srs_freep(trd_); + + config_ = NULL; } // CRITICAL: This method is called AFTER the source has been added to the source pool @@ -409,7 +435,7 @@ SrsEdgeIngester::~SrsEdgeIngester() // IMPORTANT: All field initialization in this method MUST NOT cause coroutine context switches. // This prevents the race condition where multiple coroutines could create duplicate sources // for the same stream when context switches occurred during initialization. -srs_error_t SrsEdgeIngester::initialize(SrsSharedPtr s, SrsPlayEdge *e, ISrsRequest *r) +srs_error_t SrsEdgeIngester::initialize(SrsSharedPtr s, ISrsPlayEdge *e, ISrsRequest *r) { // Because source references to this object, so we should directly use the source ptr. source_ = s.get(); @@ -490,10 +516,10 @@ srs_error_t SrsEdgeIngester::do_cycle() } // Use protocol in config. - string edge_protocol = _srs_config->get_vhost_edge_protocol(req_->vhost_); + string edge_protocol = config_->get_vhost_edge_protocol(req_->vhost_); // If follow client protocol, change to protocol of client. - bool follow_client = _srs_config->get_vhost_edge_follow_client(req_->vhost_); + bool follow_client = config_->get_vhost_edge_follow_client(req_->vhost_); if (follow_client && !req_->protocol_.empty()) { edge_protocol = req_->protocol_; } @@ -676,6 +702,14 @@ srs_error_t SrsEdgeIngester::process_publish_message(SrsRtmpCommonMessage *msg, return err; } +ISrsEdgeForwarder::ISrsEdgeForwarder() +{ +} + +ISrsEdgeForwarder::~ISrsEdgeForwarder() +{ +} + SrsEdgeForwarder::SrsEdgeForwarder() { edge_ = NULL; @@ -687,6 +721,8 @@ SrsEdgeForwarder::SrsEdgeForwarder() lb_ = new SrsLbRoundRobin(); trd_ = new SrsDummyCoroutine(); queue_ = new SrsMessageQueue(); + + config_ = _srs_config; } SrsEdgeForwarder::~SrsEdgeForwarder() @@ -696,6 +732,8 @@ SrsEdgeForwarder::~SrsEdgeForwarder() srs_freep(lb_); srs_freep(trd_); srs_freep(queue_); + + config_ = NULL; } void SrsEdgeForwarder::set_queue_size(srs_utime_t queue_size) @@ -709,7 +747,7 @@ void SrsEdgeForwarder::set_queue_size(srs_utime_t queue_size) // IMPORTANT: All field initialization in this method MUST NOT cause coroutine context switches. // This prevents the race condition where multiple coroutines could create duplicate sources // for the same stream when context switches occurred during initialization. -srs_error_t SrsEdgeForwarder::initialize(SrsSharedPtr s, SrsPublishEdge *e, ISrsRequest *r) +srs_error_t SrsEdgeForwarder::initialize(SrsSharedPtr s, ISrsPublishEdge *e, ISrsRequest *r) { // Because source references to this object, so we should directly use the source ptr. source_ = s.get(); @@ -729,7 +767,7 @@ srs_error_t SrsEdgeForwarder::start() std::string url; if (true) { - SrsConfDirective *conf = _srs_config->get_vhost_edge_origin(req_->vhost_); + SrsConfDirective *conf = config_->get_vhost_edge_origin(req_->vhost_); srs_assert(conf); // select the origin. @@ -738,7 +776,7 @@ srs_error_t SrsEdgeForwarder::start() srs_net_split_hostport(server, server, port); // support vhost tranform for edge, - std::string vhost = _srs_config->get_vhost_edge_transform_vhost(req_->vhost_); + std::string vhost = config_->get_vhost_edge_transform_vhost(req_->vhost_); vhost = srs_strings_replace(vhost, "[vhost]", req_->vhost_); url = srs_net_url_encode_rtmp_url(server, port, req_->host_, vhost, req_->app_, req_->stream_, req_->param_); @@ -761,7 +799,7 @@ srs_error_t SrsEdgeForwarder::start() // For RTMP client, we pass the vhost in tcUrl when connecting, // so we publish without vhost in stream. string stream; - if ((err = sdk_->publish(_srs_config->get_chunk_size(req_->vhost_), false, &stream)) != srs_success) { + if ((err = sdk_->publish(config_->get_chunk_size(req_->vhost_), false, &stream)) != srs_success) { return srs_error_wrap(err, "sdk publish"); } @@ -905,6 +943,14 @@ srs_error_t SrsEdgeForwarder::proxy(SrsRtmpCommonMessage *msg) return err; } +ISrsPlayEdge::ISrsPlayEdge() +{ +} + +ISrsPlayEdge::~ISrsPlayEdge() +{ +} + SrsPlayEdge::SrsPlayEdge() { state_ = SrsEdgeStateInit; @@ -983,6 +1029,14 @@ srs_error_t SrsPlayEdge::on_ingest_play() return err; } +ISrsPublishEdge::ISrsPublishEdge() +{ +} + +ISrsPublishEdge::~ISrsPublishEdge() +{ +} + SrsPublishEdge::SrsPublishEdge() { state_ = SrsEdgeStateInit; diff --git a/trunk/src/app/srs_app_edge.hpp b/trunk/src/app/srs_app_edge.hpp index 9a090aa98..b8313e34a 100644 --- a/trunk/src/app/srs_app_edge.hpp +++ b/trunk/src/app/srs_app_edge.hpp @@ -33,6 +33,15 @@ class SrsHttpClient; class ISrsHttpMessage; class SrsHttpFileReader; class SrsFlvDecoder; +class ISrsAppConfig; +class ISrsBasicRtmpClient; +class ISrsHttpClient; +class ISrsFileReader; +class ISrsFlvDecoder; +class ISrsLiveSource; +class ISrsPlayEdge; +class ISrsPublishEdge; +class ISrsAppFactory; // The state of edge, auto machine enum SrsEdgeState { @@ -57,11 +66,11 @@ enum SrsEdgeUserState { }; // The upstream of edge, can be rtmp or http. -class SrsEdgeUpstream +class ISrsEdgeUpstream { public: - SrsEdgeUpstream(); - virtual ~SrsEdgeUpstream(); + ISrsEdgeUpstream(); + virtual ~ISrsEdgeUpstream(); public: virtual srs_error_t connect(ISrsRequest *r, ISrsLbRoundRobin *lb) = 0; @@ -75,13 +84,18 @@ public: virtual void kbps_sample(const char *label, srs_utime_t age) = 0; }; -class SrsEdgeRtmpUpstream : public SrsEdgeUpstream +// The RTMP upstream of edge. +class SrsEdgeRtmpUpstream : public ISrsEdgeUpstream { +private: + ISrsAppConfig *config_; + ISrsAppFactory *app_factory_; + private: // For RTMP 302, if not empty, // use this as upstream. std::string redirect_; - SrsSimpleRtmpClient *sdk_; + ISrsBasicRtmpClient *sdk_; private: // Current selected server, the ip:port. @@ -105,16 +119,21 @@ public: virtual void kbps_sample(const char *label, srs_utime_t age); }; -class SrsEdgeFlvUpstream : public SrsEdgeUpstream +// The HTTP FLV upstream of edge. +class SrsEdgeFlvUpstream : public ISrsEdgeUpstream { +private: + ISrsAppConfig *config_; + ISrsAppFactory *app_factory_; + private: std::string schema_; - SrsHttpClient *sdk_; + ISrsHttpClient *sdk_; ISrsHttpMessage *hr_; private: - SrsHttpFileReader *reader_; - SrsFlvDecoder *decoder_; + ISrsFileReader *reader_; + ISrsFlvDecoder *decoder_; private: // We might modify the request by HTTP redirect. @@ -144,26 +163,45 @@ public: virtual void kbps_sample(const char *label, srs_utime_t age); }; +// The interface for edge ingester. +class ISrsEdgeIngester +{ +public: + ISrsEdgeIngester(); + virtual ~ISrsEdgeIngester(); + +public: + // Initialize the ingester. + virtual srs_error_t initialize(SrsSharedPtr s, ISrsPlayEdge *e, ISrsRequest *r) = 0; + // Start the ingester. + virtual srs_error_t start() = 0; + // Stop the ingester. + virtual void stop() = 0; +}; + // The edge used to ingest stream from origin. -class SrsEdgeIngester : public ISrsCoroutineHandler +class SrsEdgeIngester : public ISrsCoroutineHandler, public ISrsEdgeIngester { private: - // Because source references to this object, so we should directly use the source ptr. - SrsLiveSource *source_; + ISrsAppConfig *config_; private: - SrsPlayEdge *edge_; + // Because source references to this object, so we should directly use the source ptr. + ISrsLiveSource *source_; + +private: + ISrsPlayEdge *edge_; ISrsRequest *req_; ISrsCoroutine *trd_; ISrsLbRoundRobin *lb_; - SrsEdgeUpstream *upstream_; + ISrsEdgeUpstream *upstream_; public: SrsEdgeIngester(); virtual ~SrsEdgeIngester(); public: - virtual srs_error_t initialize(SrsSharedPtr s, SrsPlayEdge *e, ISrsRequest *r); + virtual srs_error_t initialize(SrsSharedPtr s, ISrsPlayEdge *e, ISrsRequest *r); virtual srs_error_t start(); virtual void stop(); @@ -179,15 +217,38 @@ private: virtual srs_error_t process_publish_message(SrsRtmpCommonMessage *msg, std::string &redirect); }; +// The interface for edge forwarder. +class ISrsEdgeForwarder +{ +public: + ISrsEdgeForwarder(); + virtual ~ISrsEdgeForwarder(); + +public: + // Set the queue size. + virtual void set_queue_size(srs_utime_t queue_size) = 0; + // Initialize the forwarder. + virtual srs_error_t initialize(SrsSharedPtr s, ISrsPublishEdge *e, ISrsRequest *r) = 0; + // Start the forwarder. + virtual srs_error_t start() = 0; + // Stop the forwarder. + virtual void stop() = 0; + // Proxy publish stream to edge. + virtual srs_error_t proxy(SrsRtmpCommonMessage *msg) = 0; +}; + // The edge used to forward stream to origin. -class SrsEdgeForwarder : public ISrsCoroutineHandler +class SrsEdgeForwarder : public ISrsCoroutineHandler, public ISrsEdgeForwarder { private: - // Because source references to this object, so we should directly use the source ptr. - SrsLiveSource *source_; + ISrsAppConfig *config_; private: - SrsPublishEdge *edge_; + // Because source references to this object, so we should directly use the source ptr. + ISrsLiveSource *source_; + +private: + ISrsPublishEdge *edge_; ISrsRequest *req_; ISrsCoroutine *trd_; SrsSimpleRtmpClient *sdk_; @@ -208,7 +269,7 @@ public: virtual void set_queue_size(srs_utime_t queue_size); public: - virtual srs_error_t initialize(SrsSharedPtr s, SrsPublishEdge *e, ISrsRequest *r); + virtual srs_error_t initialize(SrsSharedPtr s, ISrsPublishEdge *e, ISrsRequest *r); virtual srs_error_t start(); virtual void stop(); // Interface ISrsReusableThread2Handler @@ -222,12 +283,24 @@ public: virtual srs_error_t proxy(SrsRtmpCommonMessage *msg); }; +// The interface for play edge. +class ISrsPlayEdge +{ +public: + ISrsPlayEdge(); + virtual ~ISrsPlayEdge(); + +public: + // When ingester start to play stream. + virtual srs_error_t on_ingest_play() = 0; +}; + // The play edge control service. -class SrsPlayEdge +class SrsPlayEdge : public ISrsPlayEdge { private: SrsEdgeState state_; - SrsEdgeIngester *ingester_; + ISrsEdgeIngester *ingester_; public: SrsPlayEdge(); @@ -248,12 +321,22 @@ public: virtual srs_error_t on_ingest_play(); }; +// The interface for publish edge. +class ISrsPublishEdge +{ +public: + ISrsPublishEdge(); + virtual ~ISrsPublishEdge(); + +public: +}; + // The publish edge control service. -class SrsPublishEdge +class SrsPublishEdge : public ISrsPublishEdge { private: SrsEdgeState state_; - SrsEdgeForwarder *forwarder_; + ISrsEdgeForwarder *forwarder_; public: SrsPublishEdge(); diff --git a/trunk/src/app/srs_app_factory.cpp b/trunk/src/app/srs_app_factory.cpp index 6b3b75a54..8c15cb0e9 100644 --- a/trunk/src/app/srs_app_factory.cpp +++ b/trunk/src/app/srs_app_factory.cpp @@ -6,14 +6,27 @@ #include +#include #include +#include #include #include #include +#include #include #include #include +#include #include +#include + +ISrsAppFactory::ISrsAppFactory() +{ +} + +ISrsAppFactory::~ISrsAppFactory() +{ +} SrsAppFactory::SrsAppFactory() { @@ -60,6 +73,36 @@ ISrsHourGlass *SrsAppFactory::create_hourglass(const std::string &name, ISrsHour return new SrsHourGlass(name, handler, interval); } +ISrsBasicRtmpClient *SrsAppFactory::create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto) +{ + return new SrsSimpleRtmpClient(url, cto, sto); +} + +ISrsHttpClient *SrsAppFactory::create_http_client() +{ + return new SrsHttpClient(); +} + +ISrsFileReader *SrsAppFactory::create_http_file_reader(ISrsHttpResponseReader *r) +{ + return new SrsHttpFileReader(r); +} + +ISrsFlvDecoder *SrsAppFactory::create_flv_decoder() +{ + return new SrsFlvDecoder(); +} + +ISrsRtspSendTrack *SrsAppFactory::create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) +{ + return new SrsRtspAudioSendTrack(session, track_desc); +} + +ISrsRtspSendTrack *SrsAppFactory::create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) +{ + return new SrsRtspVideoSendTrack(session, track_desc); +} + SrsFinalFactory::SrsFinalFactory() { } diff --git a/trunk/src/app/srs_app_factory.hpp b/trunk/src/app/srs_app_factory.hpp index 9afa2e43c..d39dd54f1 100644 --- a/trunk/src/app/srs_app_factory.hpp +++ b/trunk/src/app/srs_app_factory.hpp @@ -18,9 +18,40 @@ class SrsLiveSource; class ISrsOriginHub; class ISrsHourGlass; class ISrsHourGlassHandler; +class ISrsBasicRtmpClient; +class ISrsHttpClient; +class ISrsFileReader; +class ISrsFlvDecoder; +class ISrsHttpResponseReader; +class ISrsRtspSendTrack; +class ISrsRtspConnection; +class SrsRtcTrackDescription; // The factory to create app objects. -class SrsAppFactory +class ISrsAppFactory +{ +public: + ISrsAppFactory(); + virtual ~ISrsAppFactory(); + +public: + virtual ISrsFileWriter *create_file_writer() = 0; + virtual ISrsFileWriter *create_enc_file_writer() = 0; + virtual ISrsFileReader *create_file_reader() = 0; + virtual SrsPath *create_path() = 0; + virtual SrsLiveSource *create_live_source() = 0; + virtual ISrsOriginHub *create_origin_hub() = 0; + virtual ISrsHourGlass *create_hourglass(const std::string &name, ISrsHourGlassHandler *handler, srs_utime_t interval) = 0; + virtual ISrsBasicRtmpClient *create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto) = 0; + virtual ISrsHttpClient *create_http_client() = 0; + virtual ISrsFileReader *create_http_file_reader(ISrsHttpResponseReader *r) = 0; + virtual ISrsFlvDecoder *create_flv_decoder() = 0; + virtual ISrsRtspSendTrack *create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) = 0; + virtual ISrsRtspSendTrack *create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) = 0; +}; + +// The factory to create app objects. +class SrsAppFactory : public ISrsAppFactory { public: SrsAppFactory(); @@ -34,9 +65,15 @@ public: 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 ISrsHttpClient *create_http_client(); + virtual ISrsFileReader *create_http_file_reader(ISrsHttpResponseReader *r); + virtual ISrsFlvDecoder *create_flv_decoder(); + virtual ISrsRtspSendTrack *create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc); + virtual ISrsRtspSendTrack *create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc); }; -extern SrsAppFactory *_srs_app_factory; +extern ISrsAppFactory *_srs_app_factory; // The factory to create kernel objects. class SrsFinalFactory : public ISrsKernelFactory diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index 9826119d0..0cee7d04d 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -38,7 +38,7 @@ class SrsTsContext; class SrsFmp4SegmentEncoder; class ISrsHttpHooks; class ISrsAppConfig; -class SrsAppFactory; +class ISrsAppFactory; // The wrapper of m3u8 segment from specification: // @@ -186,7 +186,7 @@ class SrsHlsMuxer { private: ISrsAppConfig *config_; - SrsAppFactory *app_factory_; + ISrsAppFactory *app_factory_; private: ISrsRequest *req_; @@ -329,7 +329,7 @@ class SrsHlsFmp4Muxer { private: ISrsAppConfig *config_; - SrsAppFactory *app_factory_; + ISrsAppFactory *app_factory_; private: ISrsRequest *req_; diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp index 26f6a307a..f9e8f9b6b 100644 --- a/trunk/src/app/srs_app_rtc_conn.hpp +++ b/trunk/src/app/srs_app_rtc_conn.hpp @@ -556,7 +556,12 @@ public: // // 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, public ISrsDisposingHandler, public ISrsExpire, public ISrsRtcPacketSender, public ISrsRtcPacketReceiver, public ISrsRtcConnectionNackTimerHandler +class SrsRtcConnection : public ISrsResource, // It's a resource. + public ISrsDisposingHandler, + public ISrsExpire, + public ISrsRtcPacketSender, + public ISrsRtcPacketReceiver, + public ISrsRtcConnectionNackTimerHandler { friend class SrsSecurityTransport; diff --git a/trunk/src/app/srs_app_rtmp_conn.hpp b/trunk/src/app/srs_app_rtmp_conn.hpp index 7008aa9cd..5c7b0ce24 100644 --- a/trunk/src/app/srs_app_rtmp_conn.hpp +++ b/trunk/src/app/srs_app_rtmp_conn.hpp @@ -154,7 +154,12 @@ public: virtual const char *transport_type(); }; -class SrsRtmpConn : public ISrsConnection, public ISrsStartable, public ISrsReloadHandler, public ISrsCoroutineHandler, public ISrsExpire +// The RTMP connection, for client to publish or play stream. +class SrsRtmpConn : public ISrsConnection, // It's a resource. + public ISrsStartable, + public ISrsReloadHandler, + public ISrsCoroutineHandler, + public ISrsExpire { // For the thread to directly access any field of connection. friend class SrsPublishRecvThread; diff --git a/trunk/src/app/srs_app_rtmp_source.hpp b/trunk/src/app/srs_app_rtmp_source.hpp index e59fc93f4..563448bce 100644 --- a/trunk/src/app/srs_app_rtmp_source.hpp +++ b/trunk/src/app/srs_app_rtmp_source.hpp @@ -60,7 +60,7 @@ class ISrsHds; #endif class ISrsNgExec; class ISrsForwarder; -class SrsAppFactory; +class ISrsAppFactory; class ISrsLiveConsumer; // The time jitter algorithm: @@ -571,7 +571,7 @@ public: class SrsLiveSourceManager : public ISrsHourGlassHandler, public ISrsLiveSourceManager { private: - SrsAppFactory *app_factory_; + ISrsAppFactory *app_factory_; private: srs_mutex_t lock_; @@ -624,6 +624,16 @@ public: virtual SrsContextId pre_source_id() = 0; virtual SrsMetaCache *meta() = 0; virtual SrsRtmpFormat *format() = 0; + // The source id changed. + virtual srs_error_t on_source_id_changed(SrsContextId id) = 0; + // Publish stream event notify. + virtual srs_error_t on_publish() = 0; + virtual void on_unpublish() = 0; + // Handle media messages. + virtual srs_error_t on_audio(SrsRtmpCommonMessage *audio) = 0; + virtual srs_error_t on_video(SrsRtmpCommonMessage *video) = 0; + virtual srs_error_t on_aggregate(SrsRtmpCommonMessage *msg) = 0; + virtual srs_error_t on_meta_data(SrsRtmpCommonMessage *msg, SrsOnMetaDataPacket *metadata) = 0; }; // The live streaming source. @@ -633,7 +643,7 @@ private: ISrsAppConfig *config_; ISrsStatistic *stat_; ISrsLiveSourceHandler *handler_; - SrsAppFactory *app_factory_; + ISrsAppFactory *app_factory_; private: // For publish, it's the publish client id. diff --git a/trunk/src/app/srs_app_rtsp_conn.cpp b/trunk/src/app/srs_app_rtsp_conn.cpp index 0af38fd35..07118e962 100644 --- a/trunk/src/app/srs_app_rtsp_conn.cpp +++ b/trunk/src/app/srs_app_rtsp_conn.cpp @@ -29,6 +29,7 @@ using namespace std; #include #include #include +#include extern SrsPps *_srs_pps_snack; extern SrsPps *_srs_pps_snack2; @@ -41,7 +42,15 @@ extern SrsPps *_srs_pps_rnack2; extern SrsPps *_srs_pps_pub; extern SrsPps *_srs_pps_conn; -SrsRtspPlayStream::SrsRtspPlayStream(SrsRtspConnection *s, const SrsContextId &cid) : source_(new SrsRtspSource()) +ISrsRtspPlayStream::ISrsRtspPlayStream() +{ +} + +ISrsRtspPlayStream::~ISrsRtspPlayStream() +{ +} + +SrsRtspPlayStream::SrsRtspPlayStream(ISrsRtspConnection *s, const SrsContextId &cid) : source_(new SrsRtspSource()) { cid_ = cid; trd_ = NULL; @@ -53,6 +62,10 @@ SrsRtspPlayStream::SrsRtspPlayStream(SrsRtspConnection *s, const SrsContextId &c cache_ssrc0_ = cache_ssrc1_ = cache_ssrc2_ = 0; cache_track0_ = cache_track1_ = cache_track2_ = NULL; + + app_factory_ = _srs_app_factory; + stat_ = _srs_stat; + rtsp_sources_ = _srs_rtsp_sources; } SrsRtspPlayStream::~SrsRtspPlayStream() @@ -61,23 +74,26 @@ SrsRtspPlayStream::~SrsRtspPlayStream() srs_freep(req_); if (true) { - std::map::iterator it; + std::map::iterator it; for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { srs_freep(it->second); } } if (true) { - std::map::iterator it; + std::map::iterator it; for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { srs_freep(it->second); } } // update the statistic when client coveried. - SrsStatistic *stat = _srs_stat; // TODO: FIXME: Should finger out the err. - stat->on_disconnect(cid_.c_str(), srs_success); + stat_->on_disconnect(cid_.c_str(), srs_success); + + app_factory_ = NULL; + stat_ = NULL; + rtsp_sources_ = NULL; } srs_error_t SrsRtspPlayStream::initialize(ISrsRequest *req, std::map sub_relations) @@ -87,12 +103,11 @@ srs_error_t SrsRtspPlayStream::initialize(ISrsRequest *req, std::mapcopy(); // We must do stat the client before hooks, because hooks depends on it. - SrsStatistic *stat = _srs_stat; - if ((err = stat->on_client(cid_.c_str(), req_, session_, SrsRtcConnPlay)) != srs_success) { + if ((err = stat_->on_client(cid_.c_str(), req_, session_, SrsRtcConnPlay)) != srs_success) { return srs_error_wrap(err, "RTSP: stat client"); } - if ((err = _srs_rtsp_sources->fetch_or_create(req_, source_)) != srs_success) { + if ((err = rtsp_sources_->fetch_or_create(req_, source_)) != srs_success) { return srs_error_wrap(err, "RTSP: fetch source failed"); } @@ -101,12 +116,12 @@ srs_error_t SrsRtspPlayStream::initialize(ISrsRequest *req, std::mapsecond; if (desc->type_ == "audio") { - SrsRtspAudioSendTrack *track = new SrsRtspAudioSendTrack(session_, desc); + ISrsRtspSendTrack *track = app_factory_->create_rtsp_audio_send_track(session_, desc); audio_tracks_.insert(make_pair(ssrc, track)); } if (desc->type_ == "video") { - SrsRtspVideoSendTrack *track = new SrsRtspVideoSendTrack(session_, desc); + ISrsRtspSendTrack *track = app_factory_->create_rtsp_video_send_track(session_, desc); video_tracks_.insert(make_pair(ssrc, track)); } } @@ -125,15 +140,15 @@ void SrsRtspPlayStream::on_stream_change(SrsRtcSourceDescription *desc) if (desc && desc->audio_track_desc_ && audio_tracks_.size() == 1) { if (!audio_tracks_.empty()) { uint32_t ssrc = desc->audio_track_desc_->ssrc_; - SrsRtspAudioSendTrack *track = audio_tracks_.begin()->second; + ISrsRtspSendTrack *track = audio_tracks_.begin()->second; - if (track->track_desc_->media_->pt_of_publisher_ != desc->audio_track_desc_->media_->pt_) { - track->track_desc_->media_->pt_of_publisher_ = desc->audio_track_desc_->media_->pt_; + if (track->track_desc()->media_->pt_of_publisher_ != desc->audio_track_desc_->media_->pt_) { + track->track_desc()->media_->pt_of_publisher_ = desc->audio_track_desc_->media_->pt_; } - if (desc->audio_track_desc_->red_ && track->track_desc_->red_ && - track->track_desc_->red_->pt_of_publisher_ != desc->audio_track_desc_->red_->pt_) { - track->track_desc_->red_->pt_of_publisher_ = desc->audio_track_desc_->red_->pt_; + if (desc->audio_track_desc_->red_ && track->track_desc()->red_ && + track->track_desc()->red_->pt_of_publisher_ != desc->audio_track_desc_->red_->pt_) { + track->track_desc()->red_->pt_of_publisher_ = desc->audio_track_desc_->red_->pt_; } audio_tracks_.clear(); @@ -147,15 +162,15 @@ void SrsRtspPlayStream::on_stream_change(SrsRtcSourceDescription *desc) if (!video_tracks_.empty()) { SrsRtcTrackDescription *vdesc = desc->video_track_descs_.at(0); uint32_t ssrc = vdesc->ssrc_; - SrsRtspVideoSendTrack *track = video_tracks_.begin()->second; + ISrsRtspSendTrack *track = video_tracks_.begin()->second; - if (track->track_desc_->media_->pt_of_publisher_ != vdesc->media_->pt_) { - track->track_desc_->media_->pt_of_publisher_ = vdesc->media_->pt_; + if (track->track_desc()->media_->pt_of_publisher_ != vdesc->media_->pt_) { + track->track_desc()->media_->pt_of_publisher_ = vdesc->media_->pt_; } - if (vdesc->red_ && track->track_desc_->red_ && - track->track_desc_->red_->pt_of_publisher_ != vdesc->red_->pt_) { - track->track_desc_->red_->pt_of_publisher_ = vdesc->red_->pt_; + if (vdesc->red_ && track->track_desc()->red_ && + track->track_desc()->red_->pt_of_publisher_ != vdesc->red_->pt_) { + track->track_desc()->red_->pt_of_publisher_ = vdesc->red_->pt_; } video_tracks_.clear(); @@ -267,7 +282,7 @@ srs_error_t SrsRtspPlayStream::send_packet(SrsRtpPacket *&pkt) uint32_t ssrc = pkt->header_.get_ssrc(); // Try to find track from cache. - SrsRtspSendTrack *track = NULL; + ISrsRtspSendTrack *track = NULL; if (cache_ssrc0_ == ssrc) { track = cache_track0_; } else if (cache_ssrc1_ == ssrc) { @@ -279,12 +294,12 @@ srs_error_t SrsRtspPlayStream::send_packet(SrsRtpPacket *&pkt) // Find by original tracks and build fast cache. if (!track) { if (pkt->is_audio()) { - map::iterator it = audio_tracks_.find(ssrc); + map::iterator it = audio_tracks_.find(ssrc); if (it != audio_tracks_.end()) { track = it->second; } } else { - map::iterator it = video_tracks_.find(ssrc); + map::iterator it = video_tracks_.find(ssrc); if (it != video_tracks_.end()) { track = it->second; } @@ -324,9 +339,9 @@ void SrsRtspPlayStream::set_all_tracks_status(bool status) // set video track status if (true) { - std::map::iterator it; + std::map::iterator it; for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { - SrsRtspVideoSendTrack *track = it->second; + ISrsRtspSendTrack *track = it->second; bool previous = track->set_track_status(status); merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; @@ -335,9 +350,9 @@ void SrsRtspPlayStream::set_all_tracks_status(bool status) // set audio track status if (true) { - std::map::iterator it; + std::map::iterator it; for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { - SrsRtspAudioSendTrack *track = it->second; + ISrsRtspSendTrack *track = it->second; bool previous = track->set_track_status(status); merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; @@ -386,12 +401,21 @@ SrsRtspConnection::SrsRtspConnection(ISrsResourceManager *cm, ISrsProtocolReadWr delta_ = new SrsEphemeralDelta(); security_ = new SrsSecurity(); - _srs_rtsp_manager->subscribe(this); + rtsp_manager_ = _srs_rtsp_manager; + stat_ = _srs_stat; + config_ = _srs_config; + rtsp_sources_ = _srs_rtsp_sources; + hooks_ = _srs_hooks; +} + +void SrsRtspConnection::assemble() +{ + rtsp_manager_->subscribe(this); } SrsRtspConnection::~SrsRtspConnection() { - _srs_rtsp_manager->unsubscribe(this); + rtsp_manager_->unsubscribe(this); srs_freep(request_); srs_freep(rtsp_); @@ -418,6 +442,12 @@ SrsRtspConnection::~SrsRtspConnection() srs_freep(cache_iov_); } srs_freep(cache_buffer_); + + rtsp_manager_ = NULL; + stat_ = NULL; + config_ = NULL; + rtsp_sources_ = NULL; + hooks_ = NULL; } srs_error_t SrsRtspConnection::do_send_packet(SrsRtpPacket *pkt) @@ -495,8 +525,7 @@ srs_error_t SrsRtspConnection::cycle() err = do_cycle(); // Update statistic when done. - SrsStatistic *stat = _srs_stat; - stat->kbps_add_delta(get_id().c_str(), delta()); + stat_->kbps_add_delta(get_id().c_str(), delta()); do_teardown(); @@ -546,104 +575,116 @@ srs_error_t SrsRtspConnection::do_cycle() if ((err = rtsp_->recv_message(&req_raw)) != srs_success) { return srs_error_wrap(err, "recv message"); } - SrsUniquePtr req(req_raw); - if (req->is_options()) { - srs_trace("RTSP: OPTIONS cseq=%ld, url=%s, client=%s:%d", req->seq_, req->uri_.c_str(), ip_.c_str(), port_); - SrsUniquePtr res(new SrsRtspOptionsResponse((int)req->seq_)); - if ((err = rtsp_->send_message(res.get())) != srs_success) { - return srs_error_wrap(err, "response option"); - } - } else if (req->is_describe()) { - // create session. - if (session_id_.empty()) { - SrsRand rand; - session_id_ = rand.gen_str(8); - } - - SrsUniquePtr res(new SrsRtspDescribeResponse((int)req->seq_)); - res->session_ = session_id_; - - std::string sdp; - if ((err = do_describe(req.get(), sdp)) != srs_success) { - res->status_ = SRS_CONSTS_RTSP_InternalServerError; - if (srs_error_code(err) == ERROR_RTSP_NO_TRACK) { - res->status_ = SRS_CONSTS_RTSP_NotFound; - } else if (srs_error_code(err) == ERROR_SYSTEM_SECURITY_DENY) { - res->status_ = SRS_CONSTS_RTSP_Forbidden; - } - srs_warn("RTSP: DESCRIBE failed: %s", srs_error_desc(err).c_str()); - srs_freep(err); - } - - res->sdp_ = sdp; - if ((err = rtsp_->send_message(res.get())) != srs_success) { - return srs_error_wrap(err, "response describe"); - } - - // Filter the \r\n to \\r\\n for JSON. - std::string local_sdp_escaped = srs_strings_replace(sdp.c_str(), "\r\n", "\\r\\n"); - srs_trace("RTSP: DESCRIBE cseq=%ld, session=%s, sdp: %s", req->seq_, session_id_.c_str(), local_sdp_escaped.c_str()); - } else if (req->is_setup()) { - srs_assert(req->transport_); - - SrsUniquePtr res(new SrsRtspSetupResponse((int)req->seq_)); - res->session_ = session_id_; - - uint32_t ssrc = 0; - if ((err = do_setup(req.get(), &ssrc)) != srs_success) { - if (srs_error_code(err) == ERROR_RTSP_TRANSPORT_NOT_SUPPORTED) { - res->status_ = SRS_CONSTS_RTSP_UnsupportedTransport; - srs_warn("RTSP: SETUP failed: %s", srs_error_summary(err).c_str()); - } else { - res->status_ = SRS_CONSTS_RTSP_InternalServerError; - srs_warn("RTSP: SETUP failed: %s", srs_error_desc(err).c_str()); - } - srs_freep(err); - } - - res->transport_->copy(req->transport_); - res->session_ = session_id_; - res->ssrc_ = srs_strconv_format_int(ssrc); - res->client_port_min_ = req->transport_->client_port_min_; - res->client_port_max_ = req->transport_->client_port_max_; - // TODO: FIXME: listen local port - res->local_port_min_ = 0; - res->local_port_max_ = 0; - if ((err = rtsp_->send_message(res.get())) != srs_success) { - return srs_error_wrap(err, "response setup"); - } - srs_trace("RTSP: SETUP cseq=%ld, session=%s, transport=%s/%s/%s, ssrc=%u, client_port=%d-%d", - req->seq_, session_id_.c_str(), req->transport_->transport_.c_str(), req->transport_->profile_.c_str(), - req->transport_->lower_transport_.c_str(), ssrc, req->transport_->client_port_min_, req->transport_->client_port_max_); - } else if (req->is_play()) { - SrsUniquePtr res(new SrsRtspResponse((int)req->seq_)); - res->session_ = session_id_; - if ((err = rtsp_->send_message(res.get())) != srs_success) { - return srs_error_wrap(err, "response record"); - } - - if ((err = do_play(req.get(), this)) != srs_success) { - return srs_error_wrap(err, "prepare play"); - } - srs_trace("RTSP: PLAY cseq=%ld, session=%s, streaming started", req->seq_, session_id_.c_str()); - } else if (req->is_teardown()) { - SrsUniquePtr res(new SrsRtspResponse((int)req->seq_)); - res->session_ = session_id_; - if ((err = rtsp_->send_message(res.get())) != srs_success) { - return srs_error_wrap(err, "response teardown"); - } - - if ((err = do_teardown()) != srs_success) { - return srs_error_wrap(err, "teardown"); - } - srs_trace("RTSP: TEARDOWN cseq=%ld, session=%s, streaming stopped", req->seq_, session_id_.c_str()); + if ((err = on_rtsp_request(req_raw)) != srs_success) { + return srs_error_wrap(err, "on rtsp request"); } } return err; } +srs_error_t SrsRtspConnection::on_rtsp_request(SrsRtspRequest *req_raw) +{ + srs_error_t err = srs_success; + + SrsUniquePtr req(req_raw); + + if (req->is_options()) { + srs_trace("RTSP: OPTIONS cseq=%ld, url=%s, client=%s:%d", req->seq_, req->uri_.c_str(), ip_.c_str(), port_); + SrsUniquePtr res(new SrsRtspOptionsResponse((int)req->seq_)); + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response option"); + } + } else if (req->is_describe()) { + // create session. + if (session_id_.empty()) { + SrsRand rand; + session_id_ = rand.gen_str(8); + } + + SrsUniquePtr res(new SrsRtspDescribeResponse((int)req->seq_)); + res->session_ = session_id_; + + std::string sdp; + if ((err = do_describe(req.get(), sdp)) != srs_success) { + res->status_ = SRS_CONSTS_RTSP_InternalServerError; + if (srs_error_code(err) == ERROR_RTSP_NO_TRACK) { + res->status_ = SRS_CONSTS_RTSP_NotFound; + } else if (srs_error_code(err) == ERROR_SYSTEM_SECURITY_DENY) { + res->status_ = SRS_CONSTS_RTSP_Forbidden; + } + srs_warn("RTSP: DESCRIBE failed: %s", srs_error_desc(err).c_str()); + srs_freep(err); + } + + res->sdp_ = sdp; + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response describe"); + } + + // Filter the \r\n to \\r\\n for JSON. + std::string local_sdp_escaped = srs_strings_replace(sdp.c_str(), "\r\n", "\\r\\n"); + srs_trace("RTSP: DESCRIBE cseq=%ld, session=%s, sdp: %s", req->seq_, session_id_.c_str(), local_sdp_escaped.c_str()); + } else if (req->is_setup()) { + srs_assert(req->transport_); + + SrsUniquePtr res(new SrsRtspSetupResponse((int)req->seq_)); + res->session_ = session_id_; + + uint32_t ssrc = 0; + if ((err = do_setup(req.get(), &ssrc)) != srs_success) { + if (srs_error_code(err) == ERROR_RTSP_TRANSPORT_NOT_SUPPORTED) { + res->status_ = SRS_CONSTS_RTSP_UnsupportedTransport; + srs_warn("RTSP: SETUP failed: %s", srs_error_summary(err).c_str()); + } else { + res->status_ = SRS_CONSTS_RTSP_InternalServerError; + srs_warn("RTSP: SETUP failed: %s", srs_error_desc(err).c_str()); + } + srs_freep(err); + } + + res->transport_->copy(req->transport_); + res->session_ = session_id_; + res->ssrc_ = srs_strconv_format_int(ssrc); + res->client_port_min_ = req->transport_->client_port_min_; + res->client_port_max_ = req->transport_->client_port_max_; + // TODO: FIXME: listen local port + res->local_port_min_ = 0; + res->local_port_max_ = 0; + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response setup"); + } + srs_trace("RTSP: SETUP cseq=%ld, session=%s, transport=%s/%s/%s, ssrc=%u, client_port=%d-%d", + req->seq_, session_id_.c_str(), req->transport_->transport_.c_str(), req->transport_->profile_.c_str(), + req->transport_->lower_transport_.c_str(), ssrc, req->transport_->client_port_min_, req->transport_->client_port_max_); + } else if (req->is_play()) { + SrsUniquePtr res(new SrsRtspResponse((int)req->seq_)); + res->session_ = session_id_; + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response record"); + } + + if ((err = do_play(req.get(), this)) != srs_success) { + return srs_error_wrap(err, "prepare play"); + } + srs_trace("RTSP: PLAY cseq=%ld, session=%s, streaming started", req->seq_, session_id_.c_str()); + } else if (req->is_teardown()) { + SrsUniquePtr res(new SrsRtspResponse((int)req->seq_)); + res->session_ = session_id_; + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response teardown"); + } + + if ((err = do_teardown()) != srs_success) { + return srs_error_wrap(err, "teardown"); + } + srs_trace("RTSP: TEARDOWN cseq=%ld, session=%s, streaming stopped", req->seq_, session_id_.c_str()); + } + + return err; +} + void SrsRtspConnection::on_before_dispose(ISrsResource *c) { if (disposing_) { @@ -698,7 +739,7 @@ srs_error_t SrsRtspConnection::do_describe(SrsRtspRequest *req, std::string &sdp request_->app_, request_->stream_, request_->port_, request_->param_); // discovery vhost, resolve the vhost from config - SrsConfDirective *parsed_vhost = _srs_config->get_vhost(request_->vhost_); + SrsConfDirective *parsed_vhost = config_->get_vhost(request_->vhost_); if (parsed_vhost) { request_->vhost_ = parsed_vhost->arg0(); } @@ -711,7 +752,7 @@ srs_error_t SrsRtspConnection::do_describe(SrsRtspRequest *req, std::string &sdp return srs_error_wrap(err, "RTSP: http_hooks_on_play"); } - if ((err = _srs_rtsp_sources->fetch_or_create(request_, source_)) != srs_success) { + if ((err = rtsp_sources_->fetch_or_create(request_, source_)) != srs_success) { return srs_error_wrap(err, "create source"); } @@ -863,7 +904,7 @@ srs_error_t SrsRtspConnection::http_hooks_on_play(ISrsRequest *req) { srs_error_t err = srs_success; - if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost_)) { + if (!config_->get_vhost_http_hooks_enabled(req->vhost_)) { return err; } @@ -873,7 +914,7 @@ srs_error_t SrsRtspConnection::http_hooks_on_play(ISrsRequest *req) std::vector hooks; if (true) { - SrsConfDirective *conf = _srs_config->get_vhost_on_play(req->vhost_); + SrsConfDirective *conf = config_->get_vhost_on_play(req->vhost_); if (!conf) { return err; @@ -884,7 +925,7 @@ srs_error_t SrsRtspConnection::http_hooks_on_play(ISrsRequest *req) for (int i = 0; i < (int)hooks.size(); i++) { std::string url = hooks.at(i); - if ((err = _srs_hooks->on_play(url, req)) != srs_success) { + if ((err = hooks_->on_play(url, req)) != srs_success) { return srs_error_wrap(err, "on_play %s", url.c_str()); } } diff --git a/trunk/src/app/srs_app_rtsp_conn.hpp b/trunk/src/app/srs_app_rtsp_conn.hpp index 220565f9e..29555fb76 100644 --- a/trunk/src/app/srs_app_rtsp_conn.hpp +++ b/trunk/src/app/srs_app_rtsp_conn.hpp @@ -31,37 +31,67 @@ class SrsRtspConnection; class SrsSecurity; class SrsRtspRequest; class SrsRtspStack; +class ISrsRtspConnection; +class ISrsRtspSendTrack; +class ISrsRtspStack; +class ISrsAppFactory; +class ISrsEphemeralDelta; +class ISrsSecurity; +class ISrsStatistic; +class ISrsRtspSourceManager; +class ISrsHttpHooks; +class ISrsAppConfig; + +// The handler for RTSP play stream. +class ISrsRtspPlayStream +{ +public: + ISrsRtspPlayStream(); + virtual ~ISrsRtspPlayStream(); + +public: + virtual srs_error_t initialize(ISrsRequest *request, std::map sub_relations) = 0; + virtual srs_error_t start() = 0; + virtual void stop() = 0; + // Directly set the status of track, generally for init to set the default value. + virtual void set_all_tracks_status(bool status) = 0; +}; // A RTSP play stream, client pull and play stream from SRS. -class SrsRtspPlayStream : public ISrsCoroutineHandler, public ISrsRtcSourceChangeCallback +class SrsRtspPlayStream : public ISrsRtspPlayStream, public ISrsCoroutineHandler, public ISrsRtcSourceChangeCallback { +private: + ISrsAppFactory *app_factory_; + ISrsStatistic *stat_; + ISrsRtspSourceManager *rtsp_sources_; + private: SrsContextId cid_; ISrsCoroutine *trd_; - SrsRtspConnection *session_; + ISrsRtspConnection *session_; private: ISrsRequest *req_; SrsSharedPtr source_; // key: publish_ssrc, value: send track to process rtp/rtcp - std::map audio_tracks_; - std::map video_tracks_; + std::map audio_tracks_; + std::map video_tracks_; private: // Fast cache for tracks. uint32_t cache_ssrc0_; uint32_t cache_ssrc1_; uint32_t cache_ssrc2_; - SrsRtspSendTrack *cache_track0_; - SrsRtspSendTrack *cache_track1_; - SrsRtspSendTrack *cache_track2_; + ISrsRtspSendTrack *cache_track0_; + ISrsRtspSendTrack *cache_track1_; + ISrsRtspSendTrack *cache_track2_; private: // Whether player started. bool is_started; public: - SrsRtspPlayStream(SrsRtspConnection *s, const SrsContextId &cid); + SrsRtspPlayStream(ISrsRtspConnection *s, const SrsContextId &cid); virtual ~SrsRtspPlayStream(); public: @@ -89,7 +119,7 @@ public: }; // The handler for RTSP connection send packet. -class ISrsRtspConnection +class ISrsRtspConnection : public ISrsExpire { public: ISrsRtspConnection(); @@ -102,11 +132,17 @@ public: // A RTSP session, client request and response with RTSP. class SrsRtspConnection : public ISrsResource, // It's a resource. public ISrsDisposingHandler, - public ISrsExpire, public ISrsCoroutineHandler, public ISrsStartable, public ISrsRtspConnection { +private: + ISrsRtspSourceManager *rtsp_sources_; + ISrsResourceManager *rtsp_manager_; + ISrsStatistic *stat_; + ISrsAppConfig *config_; + ISrsHttpHooks *hooks_; + private: bool disposing_; @@ -126,22 +162,23 @@ private: // The ip and port of client. std::string ip_; int port_; - SrsRtspStack *rtsp_; + ISrsRtspStack *rtsp_; std::string session_id_; SrsSharedPtr source_; - SrsEphemeralDelta *delta_; + ISrsEphemeralDelta *delta_; ISrsProtocolReadWriter *skt_; - SrsSecurity *security_; + ISrsSecurity *security_; iovec *cache_iov_; SrsBuffer *cache_buffer_; // key: ssrc std::map tracks_; // key: ssrc std::map networks_; - SrsRtspPlayStream *player_; + ISrsRtspPlayStream *player_; public: SrsRtspConnection(ISrsResourceManager *cm, ISrsProtocolReadWriter *skt, std::string cip, int port); + void assemble(); // Construct object, to avoid call function in constructor. virtual ~SrsRtspConnection(); // interface ISrsDisposingHandler public: @@ -179,6 +216,11 @@ public: // Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); + +private: + srs_error_t do_cycle(); + srs_error_t on_rtsp_request(SrsRtspRequest *req_raw); + // Interface ISrsExpire. public: virtual void expire(); @@ -191,9 +233,6 @@ public: bool is_alive(); void alive(); -private: - srs_error_t do_cycle(); - private: srs_error_t http_hooks_on_play(ISrsRequest *req); srs_error_t get_ssrc_by_stream_id(uint32_t stream_id, uint32_t *ssrc); diff --git a/trunk/src/app/srs_app_rtsp_source.cpp b/trunk/src/app/srs_app_rtsp_source.cpp index c92f9fcdb..dfcb22da3 100644 --- a/trunk/src/app/srs_app_rtsp_source.cpp +++ b/trunk/src/app/srs_app_rtsp_source.cpp @@ -1005,6 +1005,14 @@ srs_error_t SrsRtspRtpBuilder::consume_packets(vector &pkts) return err; } +ISrsRtspSendTrack::ISrsRtspSendTrack() +{ +} + +ISrsRtspSendTrack::~ISrsRtspSendTrack() +{ +} + SrsRtspSendTrack::SrsRtspSendTrack(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc, bool is_audio) { session_ = session; @@ -1039,6 +1047,11 @@ std::string SrsRtspSendTrack::get_track_id() return track_desc_->id_; } +SrsRtcTrackDescription *SrsRtspSendTrack::track_desc() +{ + return track_desc_; +} + SrsRtspAudioSendTrack::SrsRtspAudioSendTrack(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) : SrsRtspSendTrack(session, track_desc, true) { diff --git a/trunk/src/app/srs_app_rtsp_source.hpp b/trunk/src/app/srs_app_rtsp_source.hpp index d8f7b1b92..b538c4c7e 100644 --- a/trunk/src/app/srs_app_rtsp_source.hpp +++ b/trunk/src/app/srs_app_rtsp_source.hpp @@ -267,7 +267,20 @@ private: srs_error_t consume_packets(std::vector &pkts); }; -class SrsRtspSendTrack +class ISrsRtspSendTrack +{ +public: + ISrsRtspSendTrack(); + virtual ~ISrsRtspSendTrack(); + +public: + virtual bool set_track_status(bool active) = 0; + virtual std::string get_track_id() = 0; + virtual SrsRtcTrackDescription *track_desc() = 0; + virtual srs_error_t on_rtp(SrsRtpPacket *pkt) = 0; +}; + +class SrsRtspSendTrack : public ISrsRtspSendTrack { public: // send track description @@ -287,9 +300,7 @@ public: bool set_track_status(bool active); bool get_track_status(); std::string get_track_id(); - -public: - virtual srs_error_t on_rtp(SrsRtpPacket *pkt) = 0; + virtual SrsRtcTrackDescription *track_desc(); }; class SrsRtspAudioSendTrack : public SrsRtspSendTrack diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index 281b00e43..8adbe97dc 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -1541,7 +1541,9 @@ srs_error_t SrsServer::do_on_tcp_client(ISrsListener *listener, srs_netfd_t &stf resource = new SrsRtcTcpConn(new SrsTcpConnection(stfd2), ip, port); #ifdef SRS_RTSP } else if (listener == rtsp_listener_) { - resource = new SrsRtspConnection(conn_manager_, new SrsTcpConnection(stfd2), ip, port); + SrsRtspConnection *conn = new SrsRtspConnection(conn_manager_, new SrsTcpConnection(stfd2), ip, port); + conn->assemble(); + resource = conn; #endif } else if (listener == exporter_listener_) { // TODO: FIXME: Maybe should support https metrics. diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 40a75dc56..02ce171f3 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -68,7 +68,7 @@ class ISrsRtspSourceManager; class ISrsLog; class ISrsStatistic; class ISrsHourGlass; -class SrsAppFactory; +class ISrsAppFactory; // Initialize global shared variables cross all threads. extern srs_error_t srs_global_initialize(); @@ -110,7 +110,7 @@ private: #endif ISrsLog *log_; ISrsStatistic *stat_; - SrsAppFactory *app_factory_; + ISrsAppFactory *app_factory_; private: ISrsHttpServeMux *http_api_mux_; diff --git a/trunk/src/app/srs_app_srt_conn.hpp b/trunk/src/app/srs_app_srt_conn.hpp index f1726284d..c041d1811 100644 --- a/trunk/src/app/srs_app_srt_conn.hpp +++ b/trunk/src/app/srs_app_srt_conn.hpp @@ -77,7 +77,11 @@ private: srs_error_t recv_err_; }; -class SrsMpegtsSrtConn : public ISrsConnection, public ISrsStartable, public ISrsCoroutineHandler, public ISrsExpire +// The SRT connection, for client to publish or play stream. +class SrsMpegtsSrtConn : public ISrsConnection, // It's a resource. + public ISrsStartable, + public ISrsCoroutineHandler, + public ISrsExpire { public: SrsMpegtsSrtConn(ISrsResourceManager *resource_manager, srs_srt_t srt_fd, std::string ip, int port); diff --git a/trunk/src/kernel/srs_kernel_flv.cpp b/trunk/src/kernel/srs_kernel_flv.cpp index a18c915f4..04d2a79c0 100644 --- a/trunk/src/kernel/srs_kernel_flv.cpp +++ b/trunk/src/kernel/srs_kernel_flv.cpp @@ -667,6 +667,14 @@ srs_error_t SrsFlvTransmuxer::write_tag(char *header, int header_size, char *tag return err; } +ISrsFlvDecoder::ISrsFlvDecoder() +{ +} + +ISrsFlvDecoder::~ISrsFlvDecoder() +{ +} + SrsFlvDecoder::SrsFlvDecoder() { reader_ = NULL; diff --git a/trunk/src/kernel/srs_kernel_flv.hpp b/trunk/src/kernel/srs_kernel_flv.hpp index 10f418b4c..a4bce479f 100644 --- a/trunk/src/kernel/srs_kernel_flv.hpp +++ b/trunk/src/kernel/srs_kernel_flv.hpp @@ -340,8 +340,28 @@ private: virtual srs_error_t write_tag(char *header, int header_size, char *tag, int tag_size); }; +// The interface for FLV decoder. +class ISrsFlvDecoder +{ +public: + ISrsFlvDecoder(); + virtual ~ISrsFlvDecoder(); + +public: + // Initialize the underlayer file stream. + virtual srs_error_t initialize(ISrsReader *fr) = 0; + // Read the flv header. + virtual srs_error_t read_header(char header[9]) = 0; + // Read the tag header infos. + virtual srs_error_t read_tag_header(char *ptype, int32_t *pdata_size, uint32_t *ptime) = 0; + // Read the tag data. + virtual srs_error_t read_tag_data(char *data, int32_t size) = 0; + // Read the 4bytes previous tag size. + virtual srs_error_t read_previous_tag_size(char previous_tag_size[4]) = 0; +}; + // Decode flv file. -class SrsFlvDecoder +class SrsFlvDecoder : public ISrsFlvDecoder { private: ISrsReader *reader_; diff --git a/trunk/src/kernel/srs_kernel_kbps.cpp b/trunk/src/kernel/srs_kernel_kbps.cpp index 202281198..c20fe352f 100644 --- a/trunk/src/kernel/srs_kernel_kbps.cpp +++ b/trunk/src/kernel/srs_kernel_kbps.cpp @@ -612,6 +612,14 @@ ISrsKbpsDelta::~ISrsKbpsDelta() { } +ISrsEphemeralDelta::ISrsEphemeralDelta() +{ +} + +ISrsEphemeralDelta::~ISrsEphemeralDelta() +{ +} + SrsEphemeralDelta::SrsEphemeralDelta() { in_ = out_ = 0; @@ -636,6 +644,14 @@ void SrsEphemeralDelta::remark(int64_t *in, int64_t *out) in_ = out_ = 0; } +ISrsNetworkDelta::ISrsNetworkDelta() +{ +} + +ISrsNetworkDelta::~ISrsNetworkDelta() +{ +} + SrsNetworkDelta::SrsNetworkDelta() { in_ = out_ = NULL; diff --git a/trunk/src/kernel/srs_kernel_kbps.hpp b/trunk/src/kernel/srs_kernel_kbps.hpp index dbae05057..879af5f95 100644 --- a/trunk/src/kernel/srs_kernel_kbps.hpp +++ b/trunk/src/kernel/srs_kernel_kbps.hpp @@ -300,9 +300,20 @@ public: virtual void remark(int64_t *in, int64_t *out) = 0; }; +// The interface which provices delta of bytes. For example, we got a delta from a UDP client: +class ISrsEphemeralDelta : public ISrsKbpsDelta +{ +public: + ISrsEphemeralDelta(); + virtual ~ISrsEphemeralDelta(); + +public: + virtual void add_delta(int64_t in, int64_t out) = 0; +}; + // A delta data source for SrsKbps, used in ephemeral case, for example, UDP server to increase stat when received or // sent out each UDP packet. -class SrsEphemeralDelta : public ISrsKbpsDelta +class SrsEphemeralDelta : public ISrsEphemeralDelta { private: uint64_t in_; @@ -319,8 +330,19 @@ public: virtual void remark(int64_t *in, int64_t *out); }; +// The interface which provices delta of bytes. For example, we got a delta from a TCP client: +class ISrsNetworkDelta : public ISrsKbpsDelta +{ +public: + ISrsNetworkDelta(); + virtual ~ISrsNetworkDelta(); + +public: + virtual void set_io(ISrsProtocolStatistic *in, ISrsProtocolStatistic *out) = 0; +}; + // A network delta data source for SrsKbps. -class SrsNetworkDelta : public ISrsKbpsDelta +class SrsNetworkDelta : public ISrsNetworkDelta { private: ISrsProtocolStatistic *in_; diff --git a/trunk/src/main/srs_main_server.cpp b/trunk/src/main/srs_main_server.cpp index cf7e237f3..5003818dd 100644 --- a/trunk/src/main/srs_main_server.cpp +++ b/trunk/src/main/srs_main_server.cpp @@ -61,7 +61,7 @@ SrsConfig *_srs_config = NULL; // @global kernel factory. ISrsKernelFactory *_srs_kernel_factory = new SrsFinalFactory(); -SrsAppFactory *_srs_app_factory = new SrsAppFactory(); +ISrsAppFactory *_srs_app_factory = new SrsAppFactory(); // @global version of srs, which can grep keyword "XCORE" extern const char *_srs_version; diff --git a/trunk/src/protocol/srs_protocol_http_client.cpp b/trunk/src/protocol/srs_protocol_http_client.cpp index cb48b8e7d..a3a1c3e0d 100644 --- a/trunk/src/protocol/srs_protocol_http_client.cpp +++ b/trunk/src/protocol/srs_protocol_http_client.cpp @@ -267,6 +267,14 @@ srs_error_t SrsSslClient::write(void *plaintext, size_t nn_plaintext, ssize_t *n return err; } +ISrsHttpClient::ISrsHttpClient() +{ +} + +ISrsHttpClient::~ISrsHttpClient() +{ +} + SrsHttpClient::SrsHttpClient() { transport_ = NULL; diff --git a/trunk/src/protocol/srs_protocol_http_client.hpp b/trunk/src/protocol/srs_protocol_http_client.hpp index 384f4d922..2b4f67692 100644 --- a/trunk/src/protocol/srs_protocol_http_client.hpp +++ b/trunk/src/protocol/srs_protocol_http_client.hpp @@ -53,6 +53,26 @@ public: virtual srs_error_t write(void *buf, size_t size, ssize_t *nwrite); }; +// The interface for http client. +class ISrsHttpClient +{ +public: + ISrsHttpClient(); + virtual ~ISrsHttpClient(); + +public: + // Initialize the client. + virtual srs_error_t initialize(std::string schema, std::string h, int p, srs_utime_t tm = SRS_HTTP_CLIENT_TIMEOUT) = 0; + // Get data from the uri. + virtual srs_error_t get(std::string path, std::string req, ISrsHttpMessage **ppmsg) = 0; + // Post data to the uri. + virtual srs_error_t post(std::string path, std::string req, ISrsHttpMessage **ppmsg) = 0; + // Set receive timeout. + virtual void set_recv_timeout(srs_utime_t tm) = 0; + // Sample kbps for statistics. + virtual void kbps_sample(const char *label, srs_utime_t age) = 0; +}; + // The client to GET/POST/PUT/DELETE over HTTP. // @remark We will reuse the TCP transport until initialize or channel error, // such as send/recv failed. @@ -60,7 +80,7 @@ public: // SrsHttpClient hc; // hc.initialize("127.0.0.1", 80, 9000); // hc.post("/api/v1/version", "Hello world!", NULL); -class SrsHttpClient +class SrsHttpClient : public ISrsHttpClient { private: // The underlayer TCP transport, set to NULL when disconnect, or never not NULL when connected. diff --git a/trunk/src/protocol/srs_protocol_rtmp_conn.cpp b/trunk/src/protocol/srs_protocol_rtmp_conn.cpp index 39ed92fb5..718ce4e7e 100644 --- a/trunk/src/protocol/srs_protocol_rtmp_conn.cpp +++ b/trunk/src/protocol/srs_protocol_rtmp_conn.cpp @@ -15,6 +15,14 @@ using namespace std; #include #include +ISrsBasicRtmpClient::ISrsBasicRtmpClient() +{ +} + +ISrsBasicRtmpClient::~ISrsBasicRtmpClient() +{ +} + SrsBasicRtmpClient::SrsBasicRtmpClient(string r, srs_utime_t ctm, srs_utime_t stm) { kbps_ = new SrsNetworkKbps(); diff --git a/trunk/src/protocol/srs_protocol_rtmp_conn.hpp b/trunk/src/protocol/srs_protocol_rtmp_conn.hpp index 7073ef783..0f9b6bb5b 100644 --- a/trunk/src/protocol/srs_protocol_rtmp_conn.hpp +++ b/trunk/src/protocol/srs_protocol_rtmp_conn.hpp @@ -21,6 +21,44 @@ class SrsNetworkKbps; class SrsWallClock; class SrsAmf0Object; +// The basic RTMP client interface. +class ISrsBasicRtmpClient +{ +public: + ISrsBasicRtmpClient(); + virtual ~ISrsBasicRtmpClient(); + +public: + // Connect, handshake and connect app to RTMP server. + virtual srs_error_t connect() = 0; + // Close the connection. + virtual void close() = 0; + +public: + // Publish stream to RTMP server. + virtual srs_error_t publish(int chunk_size, bool with_vhost = true, std::string *pstream = NULL) = 0; + // Play stream from RTMP server. + virtual srs_error_t play(int chunk_size, bool with_vhost = true, std::string *pstream = NULL) = 0; + // Sample kbps for statistics. + virtual void kbps_sample(const char *label, srs_utime_t age) = 0; + // Get stream ID. + virtual int sid() = 0; + +public: + // Receive RTMP message from server. + virtual srs_error_t recv_message(SrsRtmpCommonMessage **pmsg) = 0; + // Decode RTMP message to packet. + virtual srs_error_t decode_message(SrsRtmpCommonMessage *msg, SrsRtmpCommand **ppacket) = 0; + // Send media messages to server. + virtual srs_error_t send_and_free_messages(SrsMediaPacket **msgs, int nb_msgs) = 0; + // Send media message to server. + virtual srs_error_t send_and_free_message(SrsMediaPacket *msg) = 0; + +public: + // Set receive timeout. + virtual void set_recv_timeout(srs_utime_t timeout) = 0; +}; + // The simple RTMP client, provides friendly APIs. // @remark Should never use client when closed. // Usage: @@ -28,7 +66,7 @@ class SrsAmf0Object; // client.connect(); // client.play(); // client.close(); -class SrsBasicRtmpClient +class SrsBasicRtmpClient : public ISrsBasicRtmpClient { private: std::string url_; diff --git a/trunk/src/protocol/srs_protocol_rtsp_stack.cpp b/trunk/src/protocol/srs_protocol_rtsp_stack.cpp index 19d743c1e..ad6900041 100644 --- a/trunk/src/protocol/srs_protocol_rtsp_stack.cpp +++ b/trunk/src/protocol/srs_protocol_rtsp_stack.cpp @@ -399,6 +399,14 @@ srs_error_t SrsRtspPlayResponse::encode_header(stringstream &ss) return srs_success; } +ISrsRtspStack::ISrsRtspStack() +{ +} + +ISrsRtspStack::~ISrsRtspStack() +{ +} + SrsRtspStack::SrsRtspStack(ISrsProtocolReadWriter *s) { buf_ = new SrsSimpleStream(); diff --git a/trunk/src/protocol/srs_protocol_rtsp_stack.hpp b/trunk/src/protocol/srs_protocol_rtsp_stack.hpp index 2051e3610..ba3a1be6e 100644 --- a/trunk/src/protocol/srs_protocol_rtsp_stack.hpp +++ b/trunk/src/protocol/srs_protocol_rtsp_stack.hpp @@ -345,8 +345,27 @@ protected: virtual srs_error_t encode_header(std::stringstream &ss); }; +// The interface for rtsp stack. +class ISrsRtspStack +{ +public: + ISrsRtspStack(); + virtual ~ISrsRtspStack(); + +public: + // Recv rtsp message from underlayer io. + // @param preq the output rtsp request message, which user must free it. + // @return an int error code. + // ERROR_RTSP_REQUEST_HEADER_EOF indicates request header EOF. + virtual srs_error_t recv_message(SrsRtspRequest **preq) = 0; + // Send rtsp message over underlayer io. + // @param res the rtsp response message, which user should never free it. + // @return an int error code. + virtual srs_error_t send_message(SrsRtspResponse *res) = 0; +}; + // The rtsp protocol stack to parse the rtsp packets. -class SrsRtspStack +class SrsRtspStack : public ISrsRtspStack { private: // The cached bytes buffer. diff --git a/trunk/src/utest/srs_utest.cpp b/trunk/src/utest/srs_utest.cpp index 5e866a625..12faff8f8 100644 --- a/trunk/src/utest/srs_utest.cpp +++ b/trunk/src/utest/srs_utest.cpp @@ -51,7 +51,7 @@ bool _srs_config_by_env = false; // @global kernel factory. ISrsKernelFactory *_srs_kernel_factory = new SrsFinalFactory(); -SrsAppFactory *_srs_app_factory = new SrsAppFactory(); +ISrsAppFactory *_srs_app_factory = new SrsAppFactory(); // The binary name of SRS. const char *_srs_binary = NULL; diff --git a/trunk/src/utest/srs_utest_app10.cpp b/trunk/src/utest/srs_utest_app10.cpp index f7f4592ad..ae50e00bb 100644 --- a/trunk/src/utest/srs_utest_app10.cpp +++ b/trunk/src/utest/srs_utest_app10.cpp @@ -606,7 +606,7 @@ VOID TEST(ServerTest, SetupTicksWithStatsAndHeartbeat) // Create and inject mock app factory MockAppFactoryForSetupTicks *mock_factory = new MockAppFactoryForSetupTicks(); - SrsAppFactory *original_factory = server->app_factory_; + ISrsAppFactory *original_factory = server->app_factory_; server->app_factory_ = mock_factory; // Test major use scenario: setup_ticks with stats and heartbeat enabled diff --git a/trunk/src/utest/srs_utest_app10.hpp b/trunk/src/utest/srs_utest_app10.hpp index 80c07e5e0..9de1719a9 100644 --- a/trunk/src/utest/srs_utest_app10.hpp +++ b/trunk/src/utest/srs_utest_app10.hpp @@ -156,7 +156,7 @@ public: virtual void untick(int event); }; -// Mock SrsAppFactory for testing SrsServer::setup_ticks() +// Mock ISrsAppFactory for testing SrsServer::setup_ticks() class MockAppFactoryForSetupTicks : public SrsAppFactory { public: diff --git a/trunk/src/utest/srs_utest_app12.cpp b/trunk/src/utest/srs_utest_app12.cpp index 0c23891a9..a1f4cf7a9 100644 --- a/trunk/src/utest/srs_utest_app12.cpp +++ b/trunk/src/utest/srs_utest_app12.cpp @@ -3372,6 +3372,11 @@ srs_error_t MockRtspConnection::do_send_packet(SrsRtpPacket *pkt) return srs_error_copy(send_error_); } +void MockRtspConnection::expire() +{ + // Mock implementation - does nothing for testing purposes +} + void MockRtspConnection::set_send_error(srs_error_t err) { srs_freep(send_error_); diff --git a/trunk/src/utest/srs_utest_app12.hpp b/trunk/src/utest/srs_utest_app12.hpp index 775d51bff..bed8d5d8f 100644 --- a/trunk/src/utest/srs_utest_app12.hpp +++ b/trunk/src/utest/srs_utest_app12.hpp @@ -152,6 +152,7 @@ public: MockRtspConnection(); virtual ~MockRtspConnection(); virtual srs_error_t do_send_packet(SrsRtpPacket *pkt); + virtual void expire(); void set_send_error(srs_error_t err); void reset(); }; diff --git a/trunk/src/utest/srs_utest_app13.cpp b/trunk/src/utest/srs_utest_app13.cpp new file mode 100644 index 000000000..d07f43bd6 --- /dev/null +++ b/trunk/src/utest/srs_utest_app13.cpp @@ -0,0 +1,2866 @@ +// +// 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 + +// Mock request implementation +MockEdgeRequest::MockEdgeRequest(std::string vhost, std::string app, std::string stream) +{ + vhost_ = vhost; + app_ = app; + stream_ = stream; + host_ = "127.0.0.1"; + port_ = 1935; + param_ = ""; + schema_ = "rtmp"; + tcUrl_ = "rtmp://127.0.0.1:1935/" + app; + pageUrl_ = ""; + swfUrl_ = ""; + objectEncoding_ = 0; + duration_ = -1; + args_ = NULL; + protocol_ = "rtmp"; + ip_ = "127.0.0.1"; +} + +MockEdgeRequest::~MockEdgeRequest() +{ + srs_freep(args_); +} + +ISrsRequest *MockEdgeRequest::copy() +{ + MockEdgeRequest *req = new MockEdgeRequest(vhost_, app_, stream_); + req->tcUrl_ = tcUrl_; + req->pageUrl_ = pageUrl_; + req->swfUrl_ = swfUrl_; + req->objectEncoding_ = objectEncoding_; + req->schema_ = schema_; + req->host_ = host_; + req->port_ = port_; + req->param_ = param_; + req->duration_ = duration_; + req->protocol_ = protocol_; + req->ip_ = ip_; + return req; +} + +std::string MockEdgeRequest::get_stream_url() +{ + if (vhost_ == "__defaultVhost__" || vhost_.empty()) { + return "/" + app_ + "/" + stream_; + } else { + return vhost_ + "/" + app_ + "/" + stream_; + } +} + +void MockEdgeRequest::update_auth(ISrsRequest *req) +{ +} + +void MockEdgeRequest::strip() +{ +} + +ISrsRequest *MockEdgeRequest::as_http() +{ + return this; +} + +// MockEdgeConfig implementation +MockEdgeConfig::MockEdgeConfig() +{ + edge_origin_directive_ = NULL; + edge_transform_vhost_ = ""; + chunk_size_ = 60000; +} + +MockEdgeConfig::~MockEdgeConfig() +{ + reset(); +} + +void MockEdgeConfig::reset() +{ + srs_freep(edge_origin_directive_); +} + +SrsConfDirective *MockEdgeConfig::get_vhost_edge_origin(std::string vhost) +{ + return edge_origin_directive_; +} + +std::string MockEdgeConfig::get_vhost_edge_transform_vhost(std::string vhost) +{ + return edge_transform_vhost_; +} + +int MockEdgeConfig::get_chunk_size(std::string vhost) +{ + return chunk_size_; +} + +srs_utime_t MockEdgeConfig::get_vhost_edge_origin_connect_timeout(std::string vhost) +{ + return 3 * SRS_UTIME_SECONDS; +} + +srs_utime_t MockEdgeConfig::get_vhost_edge_origin_stream_timeout(std::string vhost) +{ + return 30 * SRS_UTIME_SECONDS; +} + +// MockEdgeRtmpClient implementation +MockEdgeRtmpClient::MockEdgeRtmpClient() +{ + connect_called_ = false; + play_called_ = false; + close_called_ = false; + recv_message_called_ = false; + decode_message_called_ = false; + set_recv_timeout_called_ = false; + kbps_sample_called_ = false; + connect_error_ = srs_success; + play_error_ = srs_success; + play_stream_ = ""; + recv_timeout_ = 0; + kbps_label_ = ""; + kbps_age_ = 0; +} + +MockEdgeRtmpClient::~MockEdgeRtmpClient() +{ +} + +srs_error_t MockEdgeRtmpClient::connect() +{ + connect_called_ = true; + return srs_error_copy(connect_error_); +} + +void MockEdgeRtmpClient::close() +{ + close_called_ = true; +} + +srs_error_t MockEdgeRtmpClient::publish(int chunk_size, bool with_vhost, std::string *pstream) +{ + return srs_success; +} + +srs_error_t MockEdgeRtmpClient::play(int chunk_size, bool with_vhost, std::string *pstream) +{ + play_called_ = true; + if (pstream) { + *pstream = "livestream"; // Return the stream name + play_stream_ = *pstream; + } + return srs_error_copy(play_error_); +} + +void MockEdgeRtmpClient::kbps_sample(const char *label, srs_utime_t age) +{ + kbps_sample_called_ = true; + kbps_label_ = label; + kbps_age_ = age; +} + +int MockEdgeRtmpClient::sid() +{ + return 1; +} + +srs_error_t MockEdgeRtmpClient::recv_message(SrsRtmpCommonMessage **pmsg) +{ + recv_message_called_ = true; + return srs_success; +} + +srs_error_t MockEdgeRtmpClient::decode_message(SrsRtmpCommonMessage *msg, SrsRtmpCommand **ppacket) +{ + decode_message_called_ = true; + return srs_success; +} + +srs_error_t MockEdgeRtmpClient::send_and_free_messages(SrsMediaPacket **msgs, int nb_msgs) +{ + return srs_success; +} + +srs_error_t MockEdgeRtmpClient::send_and_free_message(SrsMediaPacket *msg) +{ + return srs_success; +} + +void MockEdgeRtmpClient::set_recv_timeout(srs_utime_t timeout) +{ + set_recv_timeout_called_ = true; + recv_timeout_ = timeout; +} + +// MockEdgeAppFactory implementation +MockEdgeAppFactory::MockEdgeAppFactory() +{ + mock_client_ = NULL; +} + +MockEdgeAppFactory::~MockEdgeAppFactory() +{ + // Don't delete mock_client_ here, it will be managed by the test +} + +ISrsBasicRtmpClient *MockEdgeAppFactory::create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto) +{ + // Return the pre-configured mock client + return mock_client_; +} + +// Test SrsEdgeRtmpUpstream::connect() - major use scenario +// This test covers the typical edge server connecting to origin server scenario: +// 1. Edge server receives a play request +// 2. Uses load balancer to select an origin server +// 3. Connects to the origin server via RTMP +// 4. Plays the stream from origin +// +// This test uses mocks to avoid needing a real RTMP server. +VOID TEST(EdgeRtmpUpstreamTest, ConnectToOriginWithLoadBalancing) +{ + srs_error_t err; + + // Create mock request for edge pull + SrsUniquePtr req(new MockEdgeRequest("test.vhost", "live", "livestream")); + req->host_ = "192.168.1.100"; + req->param_ = "?token=abc123"; + + // Create mock config with origin servers + SrsUniquePtr config(new MockEdgeConfig()); + config->edge_origin_directive_ = new SrsConfDirective(); + config->edge_origin_directive_->name_ = "origin"; + config->edge_origin_directive_->args_.push_back("192.168.1.10:1935"); + config->edge_origin_directive_->args_.push_back("192.168.1.11:1935"); + config->edge_origin_directive_->args_.push_back("192.168.1.12:1935"); + config->edge_transform_vhost_ = "origin.[vhost]"; + config->chunk_size_ = 60000; + + // Create mock RTMP client - use raw pointer since upstream will manage it + MockEdgeRtmpClient *mock_sdk = new MockEdgeRtmpClient(); + mock_sdk->connect_error_ = srs_success; + mock_sdk->play_error_ = srs_success; + + // Create mock app factory that returns our mock client + SrsUniquePtr mock_factory(new MockEdgeAppFactory()); + mock_factory->mock_client_ = mock_sdk; + + // Create load balancer for round-robin selection + SrsUniquePtr lb(new SrsLbRoundRobin()); + + // Create edge upstream (no redirect) + SrsUniquePtr upstream(new SrsEdgeRtmpUpstream("")); + + // Inject mock dependencies (private members are accessible in utests) + upstream->config_ = config.get(); + upstream->app_factory_ = mock_factory.get(); + + // Test: Connect to origin with load balancing + err = upstream->connect(req.get(), lb.get()); + HELPER_EXPECT_SUCCESS(err); + + // Verify mock SDK was called + EXPECT_TRUE(mock_sdk->connect_called_); + EXPECT_TRUE(mock_sdk->play_called_); + + // Verify load balancer selected first server + std::string selected_server; + int selected_port = 0; + upstream->selected(selected_server, selected_port); + EXPECT_STREQ("192.168.1.10", selected_server.c_str()); + EXPECT_EQ(1935, selected_port); + + // Verify the stream was played correctly + EXPECT_STREQ("livestream", mock_sdk->play_stream_.c_str()); + + // Note: mock_sdk will be deleted by upstream destructor via srs_freep(sdk_) +} + +// Test SrsEdgeRtmpUpstream message handling methods +// This test covers the major use scenario for edge server receiving and processing +// messages from origin server: +// 1. Set receive timeout for the connection +// 2. Receive RTMP messages from origin +// 3. Decode messages into RTMP commands +// 4. Sample bandwidth statistics +// 5. Query selected server information +// 6. Close the connection +VOID TEST(EdgeRtmpUpstreamTest, MessageHandlingAndClose) +{ + srs_error_t err; + + // Create mock request + SrsUniquePtr req(new MockEdgeRequest("test.vhost", "live", "stream")); + + // Create mock config + SrsUniquePtr config(new MockEdgeConfig()); + config->edge_origin_directive_ = new SrsConfDirective(); + config->edge_origin_directive_->name_ = "origin"; + config->edge_origin_directive_->args_.push_back("192.168.1.10:1935"); + + // Create mock RTMP client + MockEdgeRtmpClient *mock_sdk = new MockEdgeRtmpClient(); + + // Create mock app factory + SrsUniquePtr mock_factory(new MockEdgeAppFactory()); + mock_factory->mock_client_ = mock_sdk; + + // Create load balancer + SrsUniquePtr lb(new SrsLbRoundRobin()); + + // Create edge upstream + SrsUniquePtr upstream(new SrsEdgeRtmpUpstream("")); + + // Inject mock dependencies + upstream->config_ = config.get(); + upstream->app_factory_ = mock_factory.get(); + + // Connect to origin + err = upstream->connect(req.get(), lb.get()); + HELPER_EXPECT_SUCCESS(err); + + // Test 1: Set receive timeout + srs_utime_t timeout = 30 * SRS_UTIME_SECONDS; + upstream->set_recv_timeout(timeout); + EXPECT_TRUE(mock_sdk->set_recv_timeout_called_); + EXPECT_EQ(timeout, mock_sdk->recv_timeout_); + + // Test 2: Receive message from origin + SrsRtmpCommonMessage *msg = NULL; + err = upstream->recv_message(&msg); + HELPER_EXPECT_SUCCESS(err); + EXPECT_TRUE(mock_sdk->recv_message_called_); + + // Test 3: Decode message + SrsRtmpCommand *packet = NULL; + err = upstream->decode_message(msg, &packet); + HELPER_EXPECT_SUCCESS(err); + EXPECT_TRUE(mock_sdk->decode_message_called_); + + // Test 4: Sample bandwidth statistics + srs_utime_t age = 5 * SRS_UTIME_SECONDS; + upstream->kbps_sample("edge-pull", age); + EXPECT_TRUE(mock_sdk->kbps_sample_called_); + EXPECT_STREQ("edge-pull", mock_sdk->kbps_label_.c_str()); + EXPECT_EQ(age, mock_sdk->kbps_age_); + + // Test 5: Query selected server information + std::string selected_server; + int selected_port = 0; + upstream->selected(selected_server, selected_port); + EXPECT_STREQ("192.168.1.10", selected_server.c_str()); + EXPECT_EQ(1935, selected_port); + + // Test 6: Close connection + upstream->close(); + // After close(), sdk_ should be freed, so we can't check mock_sdk anymore + // The test passes if no crash occurs during close() +} + +// MockEdgeHttpClient implementation +MockEdgeHttpClient::MockEdgeHttpClient() +{ + initialize_called_ = false; + get_called_ = false; + set_recv_timeout_called_ = false; + kbps_sample_called_ = false; + initialize_error_ = srs_success; + get_error_ = srs_success; + mock_response_ = NULL; + schema_ = ""; + host_ = ""; + port_ = 0; + path_ = ""; + kbps_label_ = ""; + kbps_age_ = 0; +} + +MockEdgeHttpClient::~MockEdgeHttpClient() +{ + // Don't free mock_response_ here, it will be managed by the caller +} + +srs_error_t MockEdgeHttpClient::initialize(std::string schema, std::string h, int p, srs_utime_t tm) +{ + initialize_called_ = true; + schema_ = schema; + host_ = h; + port_ = p; + return srs_error_copy(initialize_error_); +} + +srs_error_t MockEdgeHttpClient::get(std::string path, std::string req, ISrsHttpMessage **ppmsg) +{ + get_called_ = true; + path_ = path; + if (ppmsg && mock_response_) { + *ppmsg = mock_response_; + } + return srs_error_copy(get_error_); +} + +srs_error_t MockEdgeHttpClient::post(std::string path, std::string req, ISrsHttpMessage **ppmsg) +{ + return srs_success; +} + +void MockEdgeHttpClient::set_recv_timeout(srs_utime_t tm) +{ + set_recv_timeout_called_ = true; +} + +void MockEdgeHttpClient::kbps_sample(const char *label, srs_utime_t age) +{ + kbps_sample_called_ = true; + kbps_label_ = label; + kbps_age_ = age; +} + +// MockEdgeHttpMessage implementation +MockEdgeHttpMessage::MockEdgeHttpMessage() +{ + status_code_ = 200; + header_ = new SrsHttpHeader(); + body_reader_ = NULL; +} + +MockEdgeHttpMessage::~MockEdgeHttpMessage() +{ + srs_freep(header_); + // Don't free body_reader_ here, it's managed externally +} + +uint8_t MockEdgeHttpMessage::message_type() +{ + return 0; +} + +uint8_t MockEdgeHttpMessage::method() +{ + return 0; +} + +uint16_t MockEdgeHttpMessage::status_code() +{ + return status_code_; +} + +std::string MockEdgeHttpMessage::method_str() +{ + return "GET"; +} + +bool MockEdgeHttpMessage::is_http_get() +{ + return true; +} + +bool MockEdgeHttpMessage::is_http_put() +{ + return false; +} + +bool MockEdgeHttpMessage::is_http_post() +{ + return false; +} + +bool MockEdgeHttpMessage::is_http_delete() +{ + return false; +} + +bool MockEdgeHttpMessage::is_http_options() +{ + return false; +} + +std::string MockEdgeHttpMessage::uri() +{ + return ""; +} + +std::string MockEdgeHttpMessage::url() +{ + return ""; +} + +std::string MockEdgeHttpMessage::host() +{ + return ""; +} + +std::string MockEdgeHttpMessage::path() +{ + return ""; +} + +std::string MockEdgeHttpMessage::query() +{ + return ""; +} + +std::string MockEdgeHttpMessage::ext() +{ + return ""; +} + +srs_error_t MockEdgeHttpMessage::body_read_all(std::string &body) +{ + return srs_success; +} + +ISrsHttpResponseReader *MockEdgeHttpMessage::body_reader() +{ + return body_reader_; +} + +int64_t MockEdgeHttpMessage::content_length() +{ + return -1; +} + +std::string MockEdgeHttpMessage::query_get(std::string key) +{ + return ""; +} + +SrsHttpHeader *MockEdgeHttpMessage::header() +{ + return header_; +} + +bool MockEdgeHttpMessage::is_jsonp() +{ + return false; +} + +bool MockEdgeHttpMessage::is_keep_alive() +{ + return false; +} + +std::string MockEdgeHttpMessage::parse_rest_id(std::string pattern) +{ + return ""; +} + +// MockEdgeFileReader implementation +MockEdgeFileReader::MockEdgeFileReader(const char *data, int size) +{ + data_ = new char[size]; + memcpy(data_, data, size); + size_ = size; + pos_ = 0; +} + +MockEdgeFileReader::~MockEdgeFileReader() +{ + srs_freepa(data_); +} + +srs_error_t MockEdgeFileReader::open(std::string p) +{ + return srs_success; +} + +void MockEdgeFileReader::close() +{ +} + +bool MockEdgeFileReader::is_open() +{ + return true; +} + +int64_t MockEdgeFileReader::tellg() +{ + return pos_; +} + +void MockEdgeFileReader::skip(int64_t size) +{ + pos_ += size; +} + +int64_t MockEdgeFileReader::seek2(int64_t offset) +{ + pos_ = offset; + return pos_; +} + +int64_t MockEdgeFileReader::filesize() +{ + return size_; +} + +srs_error_t MockEdgeFileReader::read(void *buf, size_t count, ssize_t *pnread) +{ + int available = size_ - pos_; + int to_read = (int)count; + if (to_read > available) { + to_read = available; + } + + if (to_read > 0) { + memcpy(buf, data_ + pos_, to_read); + pos_ += to_read; + } + + if (pnread) { + *pnread = to_read; + } + + return srs_success; +} + +srs_error_t MockEdgeFileReader::lseek(off_t offset, int whence, off_t *seeked) +{ + return srs_success; +} + +// MockPublishEdge implementation +MockPublishEdge::MockPublishEdge() +{ +} + +MockPublishEdge::~MockPublishEdge() +{ +} + +// MockEdgeFlvDecoder implementation +MockEdgeFlvDecoder::MockEdgeFlvDecoder() +{ + initialize_called_ = false; + read_header_called_ = false; + read_previous_tag_size_called_ = false; +} + +MockEdgeFlvDecoder::~MockEdgeFlvDecoder() +{ +} + +srs_error_t MockEdgeFlvDecoder::initialize(ISrsReader *fr) +{ + initialize_called_ = true; + return srs_success; +} + +srs_error_t MockEdgeFlvDecoder::read_header(char header[9]) +{ + read_header_called_ = true; + // Write FLV header: 'FLV' + version(1) + flags(5) + header_size(4) + header[0] = 'F'; + header[1] = 'L'; + header[2] = 'V'; + header[3] = 0x01; // version + header[4] = 0x05; // audio + video + header[5] = 0x00; + header[6] = 0x00; + header[7] = 0x00; + header[8] = 0x09; // header size + return srs_success; +} + +srs_error_t MockEdgeFlvDecoder::read_tag_header(char *ptype, int32_t *pdata_size, uint32_t *ptime) +{ + return srs_success; +} + +srs_error_t MockEdgeFlvDecoder::read_tag_data(char *data, int32_t size) +{ + return srs_success; +} + +srs_error_t MockEdgeFlvDecoder::read_previous_tag_size(char previous_tag_size[4]) +{ + read_previous_tag_size_called_ = true; + // Write previous tag size as 0 + previous_tag_size[0] = 0x00; + previous_tag_size[1] = 0x00; + previous_tag_size[2] = 0x00; + previous_tag_size[3] = 0x00; + return srs_success; +} + +// MockEdgeFlvAppFactory implementation +MockEdgeFlvAppFactory::MockEdgeFlvAppFactory() +{ + mock_http_client_ = NULL; + mock_file_reader_ = NULL; + mock_flv_decoder_ = NULL; +} + +MockEdgeFlvAppFactory::~MockEdgeFlvAppFactory() +{ + // Don't delete mock objects here, they will be managed by the test +} + +ISrsHttpClient *MockEdgeFlvAppFactory::create_http_client() +{ + return mock_http_client_; +} + +ISrsFileReader *MockEdgeFlvAppFactory::create_http_file_reader(ISrsHttpResponseReader *r) +{ + return mock_file_reader_; +} + +ISrsFlvDecoder *MockEdgeFlvAppFactory::create_flv_decoder() +{ + return mock_flv_decoder_; +} + +// Test SrsEdgeFlvUpstream::connect() - major use scenario +// This test covers the typical edge server connecting to origin server via HTTP-FLV: +// 1. Edge server receives a play request +// 2. Uses load balancer to select an origin server +// 3. Connects to the origin server via HTTP +// 4. Gets the FLV stream from origin +// 5. Initializes FLV decoder to read the stream +// +// This test uses mocks to avoid needing a real HTTP server. +VOID TEST(EdgeFlvUpstreamTest, ConnectToOriginWithHttpFlv) +{ + srs_error_t err; + + // Create mock request for edge pull + SrsUniquePtr req(new MockEdgeRequest("test.vhost", "live", "livestream")); + req->host_ = "192.168.1.100"; + req->param_ = "?token=abc123"; + + // Create mock config with origin servers + SrsUniquePtr config(new MockEdgeConfig()); + config->edge_origin_directive_ = new SrsConfDirective(); + config->edge_origin_directive_->name_ = "origin"; + config->edge_origin_directive_->args_.push_back("192.168.1.10:8080"); + config->edge_origin_directive_->args_.push_back("192.168.1.11:8080"); + + // Create mock HTTP response message + MockEdgeHttpMessage *mock_response = new MockEdgeHttpMessage(); + mock_response->status_code_ = 200; + + // Create mock HTTP client + MockEdgeHttpClient *mock_http_client = new MockEdgeHttpClient(); + mock_http_client->initialize_error_ = srs_success; + mock_http_client->get_error_ = srs_success; + mock_http_client->mock_response_ = mock_response; + + // Create mock file reader + MockEdgeFileReader *mock_file_reader = new MockEdgeFileReader("", 0); + + // Create mock FLV decoder + MockEdgeFlvDecoder *mock_flv_decoder = new MockEdgeFlvDecoder(); + + // Create mock app factory that returns our mock objects + SrsUniquePtr mock_factory(new MockEdgeFlvAppFactory()); + mock_factory->mock_http_client_ = mock_http_client; + mock_factory->mock_file_reader_ = mock_file_reader; + mock_factory->mock_flv_decoder_ = mock_flv_decoder; + + // Create load balancer for round-robin selection + SrsUniquePtr lb(new SrsLbRoundRobin()); + + // Create edge FLV upstream with http schema + SrsUniquePtr upstream(new SrsEdgeFlvUpstream("http")); + + // Inject mock dependencies (private members are accessible in utests) + upstream->config_ = config.get(); + upstream->app_factory_ = mock_factory.get(); + + // Test: Connect to origin with load balancing + err = upstream->connect(req.get(), lb.get()); + HELPER_EXPECT_SUCCESS(err); + + // Verify mock HTTP client was called + EXPECT_TRUE(mock_http_client->initialize_called_); + EXPECT_TRUE(mock_http_client->get_called_); + EXPECT_STREQ("http", mock_http_client->schema_.c_str()); + EXPECT_STREQ("192.168.1.10", mock_http_client->host_.c_str()); + EXPECT_EQ(8080, mock_http_client->port_); + EXPECT_STREQ("/live/livestream.flv?token=abc123", mock_http_client->path_.c_str()); + + // Verify FLV decoder was initialized and read header + EXPECT_TRUE(mock_flv_decoder->initialize_called_); + EXPECT_TRUE(mock_flv_decoder->read_header_called_); + EXPECT_TRUE(mock_flv_decoder->read_previous_tag_size_called_); + + // Verify load balancer selected first server + std::string selected_server; + int selected_port = 0; + upstream->selected(selected_server, selected_port); + EXPECT_STREQ("192.168.1.10", selected_server.c_str()); + EXPECT_EQ(8080, selected_port); + + // Clean up - set to NULL to avoid double-free + upstream->sdk_ = NULL; + upstream->hr_ = NULL; + upstream->reader_ = NULL; + upstream->decoder_ = NULL; + + srs_freep(mock_http_client); + srs_freep(mock_response); + srs_freep(mock_file_reader); + srs_freep(mock_flv_decoder); +} + +// Mock FLV decoder that returns actual FLV tag data for testing recv_message +class MockFlvDecoderWithData : public ISrsFlvDecoder +{ +public: + char tag_type_; + int32_t tag_size_; + uint32_t tag_time_; + char *tag_data_; + bool should_fail_; + +public: + MockFlvDecoderWithData() + { + tag_type_ = SrsFrameTypeScript; + tag_size_ = 0; + tag_time_ = 0; + tag_data_ = NULL; + should_fail_ = false; + } + + virtual ~MockFlvDecoderWithData() + { + // Don't free tag_data_ - it will be managed by the message + } + + virtual srs_error_t initialize(ISrsReader *fr) + { + return srs_success; + } + + virtual srs_error_t read_header(char header[9]) + { + return srs_success; + } + + virtual srs_error_t read_tag_header(char *ptype, int32_t *pdata_size, uint32_t *ptime) + { + if (should_fail_) { + return srs_error_new(ERROR_SYSTEM_IO_INVALID, "mock read tag header failed"); + } + *ptype = tag_type_; + *pdata_size = tag_size_; + *ptime = tag_time_; + return srs_success; + } + + virtual srs_error_t read_tag_data(char *data, int32_t size) + { + if (should_fail_) { + return srs_error_new(ERROR_SYSTEM_IO_INVALID, "mock read tag data failed"); + } + if (tag_data_ && size > 0) { + memcpy(data, tag_data_, size); + } + return srs_success; + } + + virtual srs_error_t read_previous_tag_size(char previous_tag_size[4]) + { + if (should_fail_) { + return srs_error_new(ERROR_SYSTEM_IO_INVALID, "mock read pts failed"); + } + previous_tag_size[0] = 0x00; + previous_tag_size[1] = 0x00; + previous_tag_size[2] = 0x00; + previous_tag_size[3] = 0x00; + return srs_success; + } +}; + +// Test SrsEdgeFlvUpstream::recv_message() and decode_message() - major use scenario +// This test covers the typical edge server receiving FLV messages from origin: +// 1. Edge server reads FLV tag header (type, size, timestamp) +// 2. Reads FLV tag data +// 3. Reads previous tag size +// 4. Creates RTMP message from FLV tag +// 5. Decodes metadata message if it's onMetaData +VOID TEST(EdgeFlvUpstreamTest, RecvAndDecodeMetadataMessage) +{ + srs_error_t err; + + // Create onMetaData packet data + // AMF0 string: "onMetaData" + // AMF0 object: {width: 1920, height: 1080} + SrsUniquePtr name(SrsAmf0Any::str(SRS_CONSTS_RTMP_ON_METADATA)); + SrsUniquePtr metadata(SrsAmf0Any::object()); + metadata->set("width", SrsAmf0Any::number(1920)); + metadata->set("height", SrsAmf0Any::number(1080)); + + int metadata_size = name->total_size() + metadata->total_size(); + char *metadata_data = new char[metadata_size]; + SrsBuffer metadata_buf(metadata_data, metadata_size); + HELPER_EXPECT_SUCCESS(name->write(&metadata_buf)); + HELPER_EXPECT_SUCCESS(metadata->write(&metadata_buf)); + + // Create mock FLV decoder with metadata tag + MockFlvDecoderWithData *mock_decoder = new MockFlvDecoderWithData(); + mock_decoder->tag_type_ = SrsFrameTypeScript; // 18 = script data + mock_decoder->tag_size_ = metadata_size; + mock_decoder->tag_time_ = 1000; // 1 second (note: script messages always have timestamp 0) + mock_decoder->tag_data_ = metadata_data; + + // Create edge FLV upstream + SrsUniquePtr upstream(new SrsEdgeFlvUpstream("http")); + upstream->decoder_ = mock_decoder; + + // Test: Receive message from FLV stream + SrsRtmpCommonMessage *msg = NULL; + err = upstream->recv_message(&msg); + HELPER_EXPECT_SUCCESS(err); + ASSERT_TRUE(msg != NULL); + + // Verify message properties + EXPECT_EQ(SrsFrameTypeScript, msg->header_.message_type_); + EXPECT_EQ(metadata_size, msg->size()); + // Note: Script messages always have timestamp 0 by design in initialize_amf0_script() + EXPECT_EQ(0, (int)msg->header_.timestamp_); + + // Test: Decode the metadata message + SrsRtmpCommand *packet = NULL; + err = upstream->decode_message(msg, &packet); + HELPER_EXPECT_SUCCESS(err); + ASSERT_TRUE(packet != NULL); + + // Verify decoded metadata packet + SrsOnMetaDataPacket *meta_packet = dynamic_cast(packet); + ASSERT_TRUE(meta_packet != NULL); + EXPECT_STREQ(SRS_CONSTS_RTMP_ON_METADATA, meta_packet->name_.c_str()); + + // Verify metadata properties + SrsAmf0Any *width = meta_packet->metadata_->get_property("width"); + ASSERT_TRUE(width != NULL); + EXPECT_TRUE(width->is_number()); + EXPECT_EQ(1920, (int)width->to_number()); + + SrsAmf0Any *height = meta_packet->metadata_->get_property("height"); + ASSERT_TRUE(height != NULL); + EXPECT_TRUE(height->is_number()); + EXPECT_EQ(1080, (int)height->to_number()); + + // Clean up + srs_freep(msg); + srs_freep(packet); + upstream->decoder_ = NULL; + srs_freep(mock_decoder); +} + +// Test SrsEdgeFlvUpstream utility methods - major use scenario +// This test covers the typical edge server operations: +// 1. Query selected origin server (selected()) +// 2. Set receive timeout for reading from origin (set_recv_timeout()) +// 3. Sample bandwidth statistics (kbps_sample()) +// 4. Clean up resources when connection closes (close()) +VOID TEST(EdgeFlvUpstreamTest, UtilityMethodsAndCleanup) +{ + + // Create mock HTTP client + MockEdgeHttpClient *mock_http_client = new MockEdgeHttpClient(); + mock_http_client->set_recv_timeout_called_ = false; + mock_http_client->kbps_sample_called_ = false; + + // Create edge FLV upstream + SrsUniquePtr upstream(new SrsEdgeFlvUpstream("http")); + + // Inject mock HTTP client + upstream->sdk_ = mock_http_client; + + // Set selected server info (simulating successful connection) + upstream->selected_ip_ = "192.168.1.100"; + upstream->selected_port_ = 8080; + + // Test 1: Query selected origin server + std::string server; + int port = 0; + upstream->selected(server, port); + EXPECT_STREQ("192.168.1.100", server.c_str()); + EXPECT_EQ(8080, port); + + // Test 2: Set receive timeout (typical edge ingester timeout) + srs_utime_t timeout = 30 * SRS_UTIME_SECONDS; + upstream->set_recv_timeout(timeout); + EXPECT_TRUE(mock_http_client->set_recv_timeout_called_); + + // Test 3: Sample bandwidth statistics + upstream->kbps_sample("edge-pull", 5 * SRS_UTIME_SECONDS); + EXPECT_TRUE(mock_http_client->kbps_sample_called_); + EXPECT_STREQ("edge-pull", mock_http_client->kbps_label_.c_str()); + EXPECT_EQ(5 * SRS_UTIME_SECONDS, mock_http_client->kbps_age_); + + // Create additional resources to test cleanup + MockEdgeHttpMessage *mock_response = new MockEdgeHttpMessage(); + MockEdgeFileReader *mock_file_reader = new MockEdgeFileReader(NULL, 0); + MockEdgeFlvDecoder *mock_flv_decoder = new MockEdgeFlvDecoder(); + MockEdgeRequest *mock_request = new MockEdgeRequest("test.vhost", "live", "stream1"); + + upstream->hr_ = mock_response; + upstream->reader_ = mock_file_reader; + upstream->decoder_ = mock_flv_decoder; + upstream->req_ = mock_request; + + // Test 4: Close and cleanup all resources + upstream->close(); + + // Verify all resources are freed (pointers should be NULL after close) + EXPECT_TRUE(upstream->sdk_ == NULL); + EXPECT_TRUE(upstream->hr_ == NULL); + EXPECT_TRUE(upstream->reader_ == NULL); + EXPECT_TRUE(upstream->decoder_ == NULL); + EXPECT_TRUE(upstream->req_ == NULL); +} + +// MockPlayEdge implementation +MockPlayEdge::MockPlayEdge() +{ + on_ingest_play_count_ = 0; + on_ingest_play_error_ = srs_success; +} + +MockPlayEdge::~MockPlayEdge() +{ + srs_freep(on_ingest_play_error_); +} + +srs_error_t MockPlayEdge::on_ingest_play() +{ + on_ingest_play_count_++; + return srs_error_copy(on_ingest_play_error_); +} + +void MockPlayEdge::reset() +{ + on_ingest_play_count_ = 0; + srs_freep(on_ingest_play_error_); +} + +VOID TEST(EdgeIngesterTest, Initialize) +{ + srs_error_t err; + + // Create mock dependencies + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + SrsUniquePtr mock_edge(new MockPlayEdge()); + + // Create mock source - use raw pointer for shared_ptr + MockLiveSource *raw_source = new MockLiveSource(); + SrsSharedPtr source_ptr(raw_source); + + // Create SrsEdgeIngester instance + SrsUniquePtr ingester(new SrsEdgeIngester()); + + // Test: Initialize the ingester with source, edge, and request + HELPER_EXPECT_SUCCESS(ingester->initialize(source_ptr, mock_edge.get(), mock_req.get())); + + // Verify: All fields are properly initialized + EXPECT_TRUE(ingester->source_ == raw_source); + EXPECT_TRUE(ingester->edge_ == mock_edge.get()); + EXPECT_TRUE(ingester->req_ == mock_req.get()); + + // Clean up: Set fields to NULL to avoid double-free + ingester->source_ = NULL; + ingester->edge_ = NULL; + ingester->req_ = NULL; +} + +// MockEdgeUpstreamForIngester implementation +MockEdgeUpstreamForIngester::MockEdgeUpstreamForIngester() +{ + decode_message_called_ = false; + decode_message_packet_ = NULL; + decode_message_error_ = srs_success; +} + +MockEdgeUpstreamForIngester::~MockEdgeUpstreamForIngester() +{ + srs_freep(decode_message_packet_); + srs_freep(decode_message_error_); +} + +srs_error_t MockEdgeUpstreamForIngester::connect(ISrsRequest *r, ISrsLbRoundRobin *lb) +{ + return srs_success; +} + +srs_error_t MockEdgeUpstreamForIngester::recv_message(SrsRtmpCommonMessage **pmsg) +{ + return srs_success; +} + +srs_error_t MockEdgeUpstreamForIngester::decode_message(SrsRtmpCommonMessage *msg, SrsRtmpCommand **ppacket) +{ + decode_message_called_ = true; + if (decode_message_packet_ != NULL) { + *ppacket = decode_message_packet_; + decode_message_packet_ = NULL; + } + return srs_error_copy(decode_message_error_); +} + +void MockEdgeUpstreamForIngester::close() +{ +} + +void MockEdgeUpstreamForIngester::selected(std::string &server, int &port) +{ +} + +void MockEdgeUpstreamForIngester::set_recv_timeout(srs_utime_t tm) +{ +} + +void MockEdgeUpstreamForIngester::kbps_sample(const char *label, srs_utime_t age) +{ +} + +void MockEdgeUpstreamForIngester::reset() +{ + decode_message_called_ = false; + srs_freep(decode_message_packet_); + srs_freep(decode_message_error_); +} + +VOID TEST(EdgeIngesterTest, ProcessPublishMessageAudioVideo) +{ + srs_error_t err; + + // Create mock dependencies + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + SrsUniquePtr mock_edge(new MockPlayEdge()); + + // Create mock source using existing MockLiveSource + MockLiveSource *raw_source = new MockLiveSource(); + SrsSharedPtr source_ptr(raw_source); + + // Create mock upstream (use raw pointer since ingester will manage it) + MockEdgeUpstreamForIngester *mock_upstream = new MockEdgeUpstreamForIngester(); + + // Create SrsEdgeIngester instance + SrsUniquePtr ingester(new SrsEdgeIngester()); + + // Initialize the ingester (this sets source_ from the shared_ptr) + HELPER_EXPECT_SUCCESS(ingester->initialize(source_ptr, mock_edge.get(), mock_req.get())); + + // Verify source_ was set correctly by initialize + EXPECT_TRUE(ingester->source_ == raw_source); + + // Inject mock upstream (free the original one first, then set our mock) + srs_freep(ingester->upstream_); + ingester->upstream_ = mock_upstream; + + // Test 1: Process audio message + { + SrsUniquePtr audio_msg(new SrsRtmpCommonMessage()); + audio_msg->header_.message_type_ = RTMP_MSG_AudioMessage; + audio_msg->header_.payload_length_ = 10; + audio_msg->header_.timestamp_ = 1000; + audio_msg->create_payload(10); + + std::string redirect; + HELPER_EXPECT_SUCCESS(ingester->process_publish_message(audio_msg.get(), redirect)); + } + + // Test 2: Process video message + { + SrsUniquePtr video_msg(new SrsRtmpCommonMessage()); + video_msg->header_.message_type_ = RTMP_MSG_VideoMessage; + video_msg->header_.payload_length_ = 20; + video_msg->header_.timestamp_ = 2000; + video_msg->create_payload(20); + + std::string redirect; + HELPER_EXPECT_SUCCESS(ingester->process_publish_message(video_msg.get(), redirect)); + } + + // Test 3: Process aggregate message + { + SrsUniquePtr aggregate_msg(new SrsRtmpCommonMessage()); + aggregate_msg->header_.message_type_ = RTMP_MSG_AggregateMessage; + aggregate_msg->header_.payload_length_ = 30; + aggregate_msg->header_.timestamp_ = 3000; + aggregate_msg->create_payload(30); + + std::string redirect; + HELPER_EXPECT_SUCCESS(ingester->process_publish_message(aggregate_msg.get(), redirect)); + } + + // Clean up: Set fields to NULL to avoid double-free + // Note: source_ is managed by source_ptr shared pointer, so we just set to NULL + // upstream_ will be freed by the ingester destructor (it calls srs_freep(upstream_)) + ingester->source_ = NULL; + ingester->edge_ = NULL; + ingester->req_ = NULL; + // Don't set upstream_ to NULL - let the destructor free it +} + +VOID TEST(EdgeForwarderTest, InitializeAndSetQueueSize) +{ + srs_error_t err; + + // Create mock objects + SrsSharedPtr source_ptr(new MockLiveSource()); + MockPublishEdge mock_edge; + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsEdgeForwarder + SrsUniquePtr forwarder(new SrsEdgeForwarder()); + + // Test initialize method + HELPER_EXPECT_SUCCESS(forwarder->initialize(source_ptr, &mock_edge, mock_req.get())); + + // Verify that fields are set correctly + EXPECT_EQ(forwarder->source_, source_ptr.get()); + EXPECT_EQ(forwarder->edge_, &mock_edge); + EXPECT_EQ(forwarder->req_, mock_req.get()); + + // Test set_queue_size method + srs_utime_t queue_size = 10 * SRS_UTIME_SECONDS; + forwarder->set_queue_size(queue_size); + + // Verify queue size is set (we can't directly check the queue, but the call should not crash) + // The actual verification would be done by checking if the queue behaves correctly with the new size + + // Clean up: Set fields to NULL to avoid double-free + forwarder->source_ = NULL; + forwarder->edge_ = NULL; + forwarder->req_ = NULL; +} + +VOID TEST(EdgeForwarderTest, ProxyVideoMessage) +{ + srs_error_t err; + + // Create mock objects + SrsSharedPtr source_ptr(new MockLiveSource()); + MockPublishEdge mock_edge; + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsEdgeForwarder + SrsUniquePtr forwarder(new SrsEdgeForwarder()); + + // Initialize the forwarder + HELPER_EXPECT_SUCCESS(forwarder->initialize(source_ptr, &mock_edge, mock_req.get())); + + // Create a simple RTMP client for testing (won't actually connect) + // We need this because proxy() calls sdk_->sid() + SrsSimpleRtmpClient *sdk = new SrsSimpleRtmpClient("rtmp://127.0.0.1:1935/live/test", 3 * SRS_UTIME_SECONDS, 9 * SRS_UTIME_SECONDS); + srs_freep(forwarder->sdk_); + forwarder->sdk_ = sdk; + + // Create a video message to proxy + SrsUniquePtr video_msg(new SrsRtmpCommonMessage()); + video_msg->header_.message_type_ = RTMP_MSG_VideoMessage; + video_msg->header_.timestamp_ = 1000; + video_msg->header_.stream_id_ = 1; + video_msg->header_.payload_length_ = 100; + + // Create payload for the message + char *payload = new char[100]; + memset(payload, 0x17, 100); // Video keyframe marker + HELPER_EXPECT_SUCCESS(video_msg->create(&video_msg->header_, payload, 100)); + + // Test proxy method - should successfully enqueue the message + HELPER_EXPECT_SUCCESS(forwarder->proxy(video_msg.get())); + + // Verify the message was enqueued (queue size should be 1) + EXPECT_EQ(1, forwarder->queue_->size()); + + // Clean up: Set fields to NULL to avoid double-free + forwarder->source_ = NULL; + forwarder->edge_ = NULL; + forwarder->req_ = NULL; +} + +MockEdgeIngester::MockEdgeIngester() +{ + initialize_called_ = false; + start_called_ = false; + stop_called_ = false; + initialize_error_ = srs_success; + start_error_ = srs_success; +} + +MockEdgeIngester::~MockEdgeIngester() +{ + srs_freep(initialize_error_); + srs_freep(start_error_); +} + +srs_error_t MockEdgeIngester::initialize(SrsSharedPtr s, ISrsPlayEdge *e, ISrsRequest *r) +{ + initialize_called_ = true; + return srs_error_copy(initialize_error_); +} + +srs_error_t MockEdgeIngester::start() +{ + start_called_ = true; + return srs_error_copy(start_error_); +} + +void MockEdgeIngester::stop() +{ + stop_called_ = true; +} + +void MockEdgeIngester::reset() +{ + initialize_called_ = false; + start_called_ = false; + stop_called_ = false; + srs_freep(initialize_error_); + srs_freep(start_error_); +} + +MockEdgeForwarder::MockEdgeForwarder() +{ + initialize_called_ = false; + start_called_ = false; + stop_called_ = false; + set_queue_size_called_ = false; + proxy_called_ = false; + queue_size_ = 0; + initialize_error_ = srs_success; + start_error_ = srs_success; + proxy_error_ = srs_success; +} + +MockEdgeForwarder::~MockEdgeForwarder() +{ + srs_freep(initialize_error_); + srs_freep(start_error_); + srs_freep(proxy_error_); +} + +void MockEdgeForwarder::set_queue_size(srs_utime_t queue_size) +{ + set_queue_size_called_ = true; + queue_size_ = queue_size; +} + +srs_error_t MockEdgeForwarder::initialize(SrsSharedPtr s, ISrsPublishEdge *e, ISrsRequest *r) +{ + initialize_called_ = true; + return srs_error_copy(initialize_error_); +} + +srs_error_t MockEdgeForwarder::start() +{ + start_called_ = true; + return srs_error_copy(start_error_); +} + +void MockEdgeForwarder::stop() +{ + stop_called_ = true; +} + +srs_error_t MockEdgeForwarder::proxy(SrsRtmpCommonMessage *msg) +{ + proxy_called_ = true; + return srs_error_copy(proxy_error_); +} + +void MockEdgeForwarder::reset() +{ + initialize_called_ = false; + start_called_ = false; + stop_called_ = false; + set_queue_size_called_ = false; + proxy_called_ = false; + queue_size_ = 0; + srs_freep(initialize_error_); + srs_freep(start_error_); + srs_freep(proxy_error_); +} + +VOID TEST(PlayEdgeTest, ClientPlayLifecycle) +{ + srs_error_t err; + + // Create mock dependencies + SrsSharedPtr source_ptr(new MockLiveSource()); + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsPlayEdge + SrsUniquePtr play_edge(new SrsPlayEdge()); + + // Replace the real ingester with mock ingester + MockEdgeIngester *mock_ingester = new MockEdgeIngester(); + srs_freep(play_edge->ingester_); + play_edge->ingester_ = mock_ingester; + + // Test initialize method + HELPER_EXPECT_SUCCESS(play_edge->initialize(source_ptr, mock_req.get())); + EXPECT_TRUE(mock_ingester->initialize_called_); + + // Test on_client_play method - should start ingester when in init state + HELPER_EXPECT_SUCCESS(play_edge->on_client_play()); + EXPECT_TRUE(mock_ingester->start_called_); + EXPECT_EQ(SrsEdgeStatePlay, play_edge->state_); + + // Test on_all_client_stop method - should stop ingester and reset state + play_edge->on_all_client_stop(); + EXPECT_TRUE(mock_ingester->stop_called_); + EXPECT_EQ(SrsEdgeStateInit, play_edge->state_); + + // Clean up: ingester_ will be freed by play_edge destructor +} + +VOID TEST(PlayEdgeTest, IngestPlayStateTransition) +{ + srs_error_t err; + + // Create mock dependencies + SrsSharedPtr source_ptr(new MockLiveSource()); + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsPlayEdge + SrsUniquePtr play_edge(new SrsPlayEdge()); + + // Replace the real ingester with mock ingester + MockEdgeIngester *mock_ingester = new MockEdgeIngester(); + srs_freep(play_edge->ingester_); + play_edge->ingester_ = mock_ingester; + + // Initialize the play edge + HELPER_EXPECT_SUCCESS(play_edge->initialize(source_ptr, mock_req.get())); + + // Client starts playing - state should transition to SrsEdgeStatePlay + HELPER_EXPECT_SUCCESS(play_edge->on_client_play()); + EXPECT_EQ(SrsEdgeStatePlay, play_edge->state_); + + // Ingester connects and starts playing - state should transition to SrsEdgeStateIngestConnected + HELPER_EXPECT_SUCCESS(play_edge->on_ingest_play()); + EXPECT_EQ(SrsEdgeStateIngestConnected, play_edge->state_); + + // Call on_ingest_play again when already connected - should return success and remain in same state + HELPER_EXPECT_SUCCESS(play_edge->on_ingest_play()); + EXPECT_EQ(SrsEdgeStateIngestConnected, play_edge->state_); + + // All clients stop - state should transition back to SrsEdgeStateInit + play_edge->on_all_client_stop(); + EXPECT_EQ(SrsEdgeStateInit, play_edge->state_); + + // Clean up: ingester_ will be freed by play_edge destructor +} + +VOID TEST(PublishEdgeTest, InitializeSetQueueSizeAndCanPublish) +{ + srs_error_t err; + + // Create mock dependencies + SrsSharedPtr source_ptr(new MockLiveSource()); + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsPublishEdge + SrsUniquePtr publish_edge(new SrsPublishEdge()); + + // Replace the real forwarder with mock forwarder + MockEdgeForwarder *mock_forwarder = new MockEdgeForwarder(); + srs_freep(publish_edge->forwarder_); + publish_edge->forwarder_ = mock_forwarder; + + // Test initial state - should be able to publish + EXPECT_TRUE(publish_edge->can_publish()); + EXPECT_EQ(SrsEdgeStateInit, publish_edge->state_); + + // Test set_queue_size method - should delegate to forwarder + srs_utime_t queue_size = 10 * SRS_UTIME_SECONDS; + publish_edge->set_queue_size(queue_size); + EXPECT_TRUE(mock_forwarder->set_queue_size_called_); + EXPECT_EQ(queue_size, mock_forwarder->queue_size_); + + // Test initialize method - should initialize forwarder + HELPER_EXPECT_SUCCESS(publish_edge->initialize(source_ptr, mock_req.get())); + EXPECT_TRUE(mock_forwarder->initialize_called_); + + // After initialization, should still be able to publish (state is still Init) + EXPECT_TRUE(publish_edge->can_publish()); + EXPECT_EQ(SrsEdgeStateInit, publish_edge->state_); + + // Clean up: forwarder_ will be freed by publish_edge destructor +} + +VOID TEST(PublishEdgeTest, ClientPublishProxyAndUnpublish) +{ + srs_error_t err; + + // Create mock dependencies + SrsSharedPtr source_ptr(new MockLiveSource()); + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsPublishEdge + SrsUniquePtr publish_edge(new SrsPublishEdge()); + + // Replace the real forwarder with mock forwarder + MockEdgeForwarder *mock_forwarder = new MockEdgeForwarder(); + srs_freep(publish_edge->forwarder_); + publish_edge->forwarder_ = mock_forwarder; + + // Initialize the publish edge + HELPER_EXPECT_SUCCESS(publish_edge->initialize(source_ptr, mock_req.get())); + EXPECT_EQ(SrsEdgeStateInit, publish_edge->state_); + + // Test on_client_publish - should start forwarder and transition to Publish state + HELPER_EXPECT_SUCCESS(publish_edge->on_client_publish()); + EXPECT_TRUE(mock_forwarder->start_called_); + EXPECT_EQ(SrsEdgeStatePublish, publish_edge->state_); + + // Test on_proxy_publish - should proxy message to forwarder + SrsUniquePtr msg(new SrsRtmpCommonMessage()); + HELPER_EXPECT_SUCCESS(publish_edge->on_proxy_publish(msg.get())); + EXPECT_TRUE(mock_forwarder->proxy_called_); + + // Test on_proxy_unpublish - should stop forwarder and transition back to Init state + publish_edge->on_proxy_unpublish(); + EXPECT_TRUE(mock_forwarder->stop_called_); + EXPECT_EQ(SrsEdgeStateInit, publish_edge->state_); + + // Clean up: forwarder_ will be freed by publish_edge destructor +} + +// MockStatisticForRtspPlayStream implementation +MockStatisticForRtspPlayStream::MockStatisticForRtspPlayStream() +{ + on_client_count_ = 0; + on_client_error_ = srs_success; +} + +MockStatisticForRtspPlayStream::~MockStatisticForRtspPlayStream() +{ + srs_freep(on_client_error_); +} + +void MockStatisticForRtspPlayStream::on_disconnect(std::string id, srs_error_t err) +{ +} + +srs_error_t MockStatisticForRtspPlayStream::on_client(std::string id, ISrsRequest *req, ISrsExpire *conn, SrsRtmpConnType type) +{ + on_client_count_++; + return srs_error_copy(on_client_error_); +} + +srs_error_t MockStatisticForRtspPlayStream::on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height) +{ + return srs_success; +} + +srs_error_t MockStatisticForRtspPlayStream::on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, SrsAudioChannels asound_type, SrsAacObjectType aac_object) +{ + return srs_success; +} + +void MockStatisticForRtspPlayStream::on_stream_publish(ISrsRequest *req, std::string publisher_id) +{ +} + +void MockStatisticForRtspPlayStream::on_stream_close(ISrsRequest *req) +{ +} + +void MockStatisticForRtspPlayStream::kbps_add_delta(std::string id, ISrsKbpsDelta *delta) +{ +} + +void MockStatisticForRtspPlayStream::kbps_sample() +{ +} + +srs_error_t MockStatisticForRtspPlayStream::on_video_frames(ISrsRequest *req, int nb_frames) +{ + return srs_success; +} + +std::string MockStatisticForRtspPlayStream::server_id() +{ + return "mock_server_id"; +} + +std::string MockStatisticForRtspPlayStream::service_id() +{ + return "mock_service_id"; +} + +std::string MockStatisticForRtspPlayStream::service_pid() +{ + return "mock_service_pid"; +} + +SrsStatisticVhost *MockStatisticForRtspPlayStream::find_vhost_by_id(std::string vid) +{ + return NULL; +} + +SrsStatisticStream *MockStatisticForRtspPlayStream::find_stream(std::string sid) +{ + return NULL; +} + +SrsStatisticClient *MockStatisticForRtspPlayStream::find_client(std::string client_id) +{ + return NULL; +} + +srs_error_t MockStatisticForRtspPlayStream::dumps_vhosts(SrsJsonArray *arr) +{ + return srs_success; +} + +srs_error_t MockStatisticForRtspPlayStream::dumps_streams(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockStatisticForRtspPlayStream::dumps_clients(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockStatisticForRtspPlayStream::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs) +{ + send_bytes = 0; + recv_bytes = 0; + nstreams = 0; + nclients = 0; + total_nclients = 0; + nerrs = 0; + return srs_success; +} + +void MockStatisticForRtspPlayStream::reset() +{ + on_client_count_ = 0; + srs_freep(on_client_error_); +} + +// MockRtspSourceManager implementation +MockRtspSourceManager::MockRtspSourceManager() +{ + fetch_or_create_count_ = 0; + fetch_or_create_error_ = srs_success; + mock_source_ = SrsSharedPtr(new SrsRtspSource()); +} + +MockRtspSourceManager::~MockRtspSourceManager() +{ + srs_freep(fetch_or_create_error_); +} + +srs_error_t MockRtspSourceManager::initialize() +{ + return srs_success; +} + +srs_error_t MockRtspSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr &pps) +{ + fetch_or_create_count_++; + if (fetch_or_create_error_ != srs_success) { + return srs_error_copy(fetch_or_create_error_); + } + pps = mock_source_; + return srs_success; +} + +SrsSharedPtr MockRtspSourceManager::fetch(ISrsRequest *r) +{ + return mock_source_; +} + +void MockRtspSourceManager::reset() +{ + fetch_or_create_count_ = 0; + srs_freep(fetch_or_create_error_); +} + +// MockRtspSendTrack implementation +MockRtspSendTrack::MockRtspSendTrack(std::string track_id, SrsRtcTrackDescription *desc) +{ + track_id_ = track_id; + track_desc_ = desc->copy(); + track_status_ = false; + on_rtp_count_ = 0; + last_ssrc_ = 0; + last_sequence_ = 0; +} + +MockRtspSendTrack::~MockRtspSendTrack() +{ + srs_freep(track_desc_); +} + +bool MockRtspSendTrack::set_track_status(bool active) +{ + bool previous = track_status_; + track_status_ = active; + return previous; +} + +std::string MockRtspSendTrack::get_track_id() +{ + return track_id_; +} + +SrsRtcTrackDescription *MockRtspSendTrack::track_desc() +{ + return track_desc_; +} + +srs_error_t MockRtspSendTrack::on_rtp(SrsRtpPacket *pkt) +{ + on_rtp_count_++; + last_ssrc_ = pkt->header_.get_ssrc(); + last_sequence_ = pkt->header_.get_sequence(); + return srs_success; +} + +void MockRtspSendTrack::reset() +{ + on_rtp_count_ = 0; + last_ssrc_ = 0; + last_sequence_ = 0; +} + +// MockRtspStack implementation +MockRtspStack::MockRtspStack() +{ + send_message_called_ = false; + last_response_seq_ = 0; + last_response_session_ = ""; + last_response_type_ = ""; + send_message_error_ = srs_success; +} + +MockRtspStack::~MockRtspStack() +{ + srs_freep(send_message_error_); +} + +srs_error_t MockRtspStack::recv_message(SrsRtspRequest **preq) +{ + return srs_success; +} + +srs_error_t MockRtspStack::send_message(SrsRtspResponse *res) +{ + send_message_called_ = true; + last_response_seq_ = (int)res->seq_; + last_response_session_ = res->session_; + + // Determine response type by dynamic_cast + if (dynamic_cast(res)) { + last_response_type_ = "OPTIONS"; + } else if (dynamic_cast(res)) { + last_response_type_ = "DESCRIBE"; + } else if (dynamic_cast(res)) { + last_response_type_ = "SETUP"; + } else { + last_response_type_ = "GENERIC"; + } + + return srs_error_copy(send_message_error_); +} + +void MockRtspStack::reset() +{ + send_message_called_ = false; + last_response_seq_ = 0; + last_response_session_ = ""; + last_response_type_ = ""; + srs_freep(send_message_error_); +} + +// MockRtspPlayStream implementation +MockRtspPlayStream::MockRtspPlayStream() +{ + initialize_called_ = false; + start_called_ = false; + stop_called_ = false; + set_all_tracks_status_called_ = false; + set_all_tracks_status_value_ = false; + initialize_error_ = srs_success; + start_error_ = srs_success; +} + +MockRtspPlayStream::~MockRtspPlayStream() +{ + srs_freep(initialize_error_); + srs_freep(start_error_); +} + +srs_error_t MockRtspPlayStream::initialize(ISrsRequest *request, std::map sub_relations) +{ + initialize_called_ = true; + return srs_error_copy(initialize_error_); +} + +srs_error_t MockRtspPlayStream::start() +{ + start_called_ = true; + return srs_error_copy(start_error_); +} + +void MockRtspPlayStream::stop() +{ + stop_called_ = true; +} + +void MockRtspPlayStream::set_all_tracks_status(bool status) +{ + set_all_tracks_status_called_ = true; + set_all_tracks_status_value_ = status; +} + +void MockRtspPlayStream::reset() +{ + initialize_called_ = false; + start_called_ = false; + stop_called_ = false; + set_all_tracks_status_called_ = false; + set_all_tracks_status_value_ = false; + srs_freep(initialize_error_); + srs_freep(start_error_); +} + +// MockAppFactoryForRtspPlayStream implementation +MockAppFactoryForRtspPlayStream::MockAppFactoryForRtspPlayStream() +{ + create_rtsp_audio_send_track_count_ = 0; + create_rtsp_video_send_track_count_ = 0; +} + +MockAppFactoryForRtspPlayStream::~MockAppFactoryForRtspPlayStream() +{ +} + +ISrsRtspSendTrack *MockAppFactoryForRtspPlayStream::create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) +{ + create_rtsp_audio_send_track_count_++; + return new MockRtspSendTrack("audio_track", track_desc); +} + +ISrsRtspSendTrack *MockAppFactoryForRtspPlayStream::create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) +{ + create_rtsp_video_send_track_count_++; + return new MockRtspSendTrack("video_track", track_desc); +} + +void MockAppFactoryForRtspPlayStream::reset() +{ + create_rtsp_audio_send_track_count_ = 0; + create_rtsp_video_send_track_count_ = 0; +} + +VOID TEST(RtspPlayStreamTest, InitializeWithAudioAndVideoTracks) +{ + srs_error_t err; + + // Create mock dependencies - these must outlive play_stream + MockRtspConnection mock_session; + MockStatisticForRtspPlayStream mock_stat; + MockRtspSourceManager mock_rtsp_sources; + MockAppFactoryForRtspPlayStream mock_app_factory; + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsRtspPlayStream + SrsContextId cid; + SrsUniquePtr play_stream(new SrsRtspPlayStream(&mock_session, cid)); + + // Inject mock dependencies + play_stream->stat_ = &mock_stat; + play_stream->rtsp_sources_ = &mock_rtsp_sources; + play_stream->app_factory_ = &mock_app_factory; + + // Create track descriptions for audio and video + SrsUniquePtr audio_desc(new SrsRtcTrackDescription()); + audio_desc->type_ = "audio"; + audio_desc->id_ = "audio_track_1"; + audio_desc->ssrc_ = 1001; + + SrsUniquePtr video_desc(new SrsRtcTrackDescription()); + video_desc->type_ = "video"; + video_desc->id_ = "video_track_1"; + video_desc->ssrc_ = 2001; + + // Create sub_relations map + std::map sub_relations; + sub_relations[1001] = audio_desc.get(); + sub_relations[2001] = video_desc.get(); + + // Test initialize method + HELPER_EXPECT_SUCCESS(play_stream->initialize(mock_req.get(), sub_relations)); + + // Verify that stat->on_client was called + EXPECT_EQ(1, mock_stat.on_client_count_); + + // Verify that rtsp_sources->fetch_or_create was called + EXPECT_EQ(1, mock_rtsp_sources.fetch_or_create_count_); + + // Verify that audio and video tracks were created + EXPECT_EQ(1, mock_app_factory.create_rtsp_audio_send_track_count_); + EXPECT_EQ(1, mock_app_factory.create_rtsp_video_send_track_count_); + + // Verify that tracks were added to the maps + EXPECT_EQ(1, (int)play_stream->audio_tracks_.size()); + EXPECT_EQ(1, (int)play_stream->video_tracks_.size()); + + // Verify the audio track + EXPECT_TRUE(play_stream->audio_tracks_.find(1001) != play_stream->audio_tracks_.end()); + ISrsRtspSendTrack *audio_track = play_stream->audio_tracks_[1001]; + EXPECT_TRUE(audio_track != NULL); + EXPECT_EQ("audio_track", audio_track->get_track_id()); + + // Verify the video track + EXPECT_TRUE(play_stream->video_tracks_.find(2001) != play_stream->video_tracks_.end()); + ISrsRtspSendTrack *video_track = play_stream->video_tracks_[2001]; + EXPECT_TRUE(video_track != NULL); + EXPECT_EQ("video_track", video_track->get_track_id()); + + // Note: play_stream will be destroyed before mocks go out of scope + // The destructor calls stat_->on_disconnect(), so stat_ must remain valid +} + +VOID TEST(RtspPlayStreamTest, OnStreamChange) +{ + srs_error_t err = srs_success; + + // Create mock dependencies + MockStatisticForRtspPlayStream mock_stat; + MockRtspSourceManager mock_rtsp_sources; + MockAppFactoryForRtspPlayStream mock_app_factory; + + // Create a mock RTSP source + mock_rtsp_sources.mock_source_ = SrsSharedPtr(new SrsRtspSource()); + + // Create mock request + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsRtspPlayStream instance + SrsContextId cid; + SrsUniquePtr play_stream(new SrsRtspPlayStream(NULL, cid)); + + // Inject mock dependencies + play_stream->stat_ = &mock_stat; + play_stream->rtsp_sources_ = &mock_rtsp_sources; + play_stream->app_factory_ = &mock_app_factory; + + // Create initial audio and video track descriptions with original SSRCs and PTs + SrsUniquePtr audio_desc(new SrsRtcTrackDescription()); + audio_desc->type_ = "audio"; + audio_desc->id_ = "audio_track"; + audio_desc->ssrc_ = 1001; + audio_desc->media_ = new SrsAudioPayload(111, "opus", 48000, 2); + audio_desc->media_->pt_of_publisher_ = 111; + audio_desc->red_ = new SrsRedPayload(63, "red", 48000, 2); + audio_desc->red_->pt_of_publisher_ = 63; + + SrsUniquePtr video_desc(new SrsRtcTrackDescription()); + video_desc->type_ = "video"; + video_desc->id_ = "video_track"; + video_desc->ssrc_ = 2001; + video_desc->media_ = new SrsVideoPayload(102, "H264", 90000); + video_desc->media_->pt_of_publisher_ = 102; + video_desc->red_ = new SrsCodecPayload(100, "rtx", 90000); + video_desc->red_->pt_of_publisher_ = 100; + + // Initialize with sub_relations + std::map sub_relations; + sub_relations[1001] = audio_desc.get(); + sub_relations[2001] = video_desc.get(); + + HELPER_EXPECT_SUCCESS(play_stream->initialize(mock_req.get(), sub_relations)); + + // Verify initial state + EXPECT_EQ(1, (int)play_stream->audio_tracks_.size()); + EXPECT_EQ(1, (int)play_stream->video_tracks_.size()); + + // Get the tracks + ISrsRtspSendTrack *audio_track = play_stream->audio_tracks_[1001]; + ISrsRtspSendTrack *video_track = play_stream->video_tracks_[2001]; + EXPECT_TRUE(audio_track != NULL); + EXPECT_TRUE(video_track != NULL); + + // Verify initial PT values + EXPECT_EQ(111, audio_track->track_desc()->media_->pt_of_publisher_); + EXPECT_EQ(63, audio_track->track_desc()->red_->pt_of_publisher_); + EXPECT_EQ(102, video_track->track_desc()->media_->pt_of_publisher_); + EXPECT_EQ(100, video_track->track_desc()->red_->pt_of_publisher_); + + // Create a new stream description with changed SSRCs and PTs (simulating stream change) + SrsUniquePtr new_desc(new SrsRtcSourceDescription()); + + // Create new audio track description with different SSRC and PT + new_desc->audio_track_desc_ = new SrsRtcTrackDescription(); + new_desc->audio_track_desc_->type_ = "audio"; + new_desc->audio_track_desc_->ssrc_ = 1002; // Changed SSRC + new_desc->audio_track_desc_->media_ = new SrsAudioPayload(112, "opus", 48000, 2); // Changed PT + new_desc->audio_track_desc_->media_->pt_ = 112; + new_desc->audio_track_desc_->red_ = new SrsRedPayload(64, "red", 48000, 2); // Changed PT + new_desc->audio_track_desc_->red_->pt_ = 64; + + // Create new video track description with different SSRC and PT + SrsRtcTrackDescription *new_video_desc = new SrsRtcTrackDescription(); + new_video_desc->type_ = "video"; + new_video_desc->ssrc_ = 2002; // Changed SSRC + new_video_desc->media_ = new SrsVideoPayload(103, "H264", 90000); // Changed PT + new_video_desc->media_->pt_ = 103; + new_video_desc->red_ = new SrsCodecPayload(101, "rtx", 90000); // Changed PT + new_video_desc->red_->pt_ = 101; + new_desc->video_track_descs_.push_back(new_video_desc); + + // Call on_stream_change + play_stream->on_stream_change(new_desc.get()); + + // Verify that audio track map was updated with new SSRC + EXPECT_EQ(1, (int)play_stream->audio_tracks_.size()); + EXPECT_TRUE(play_stream->audio_tracks_.find(1001) == play_stream->audio_tracks_.end()); // Old SSRC removed + EXPECT_TRUE(play_stream->audio_tracks_.find(1002) != play_stream->audio_tracks_.end()); // New SSRC added + + // Verify that video track map was updated with new SSRC + EXPECT_EQ(1, (int)play_stream->video_tracks_.size()); + EXPECT_TRUE(play_stream->video_tracks_.find(2001) == play_stream->video_tracks_.end()); // Old SSRC removed + EXPECT_TRUE(play_stream->video_tracks_.find(2002) != play_stream->video_tracks_.end()); // New SSRC added + + // Verify that the track objects are the same (not recreated) + EXPECT_EQ(audio_track, play_stream->audio_tracks_[1002]); + EXPECT_EQ(video_track, play_stream->video_tracks_[2002]); + + // Verify that PT values were updated + EXPECT_EQ(112, audio_track->track_desc()->media_->pt_of_publisher_); + EXPECT_EQ(64, audio_track->track_desc()->red_->pt_of_publisher_); + EXPECT_EQ(103, video_track->track_desc()->media_->pt_of_publisher_); + EXPECT_EQ(101, video_track->track_desc()->red_->pt_of_publisher_); + + // Test with NULL desc (should return early without error) + play_stream->on_stream_change(NULL); + EXPECT_EQ(1, (int)play_stream->audio_tracks_.size()); + EXPECT_EQ(1, (int)play_stream->video_tracks_.size()); +} + +VOID TEST(RtspPlayStreamTest, SendPacketWithCacheAndTrackLookup) +{ + srs_error_t err = srs_success; + + // Create mock dependencies + MockRtspConnection mock_session; + MockStatisticForRtspPlayStream mock_stat; + MockRtspSourceManager mock_rtsp_sources; + MockAppFactoryForRtspPlayStream mock_app_factory; + SrsUniquePtr mock_req(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create SrsRtspPlayStream + SrsContextId cid; + SrsUniquePtr play_stream(new SrsRtspPlayStream(&mock_session, cid)); + + // Inject mock dependencies + play_stream->stat_ = &mock_stat; + play_stream->rtsp_sources_ = &mock_rtsp_sources; + play_stream->app_factory_ = &mock_app_factory; + + // Create track descriptions for audio and video + SrsUniquePtr audio_desc(new SrsRtcTrackDescription()); + audio_desc->type_ = "audio"; + audio_desc->id_ = "audio_track_1"; + audio_desc->ssrc_ = 1001; + + SrsUniquePtr video_desc(new SrsRtcTrackDescription()); + video_desc->type_ = "video"; + video_desc->id_ = "video_track_1"; + video_desc->ssrc_ = 2001; + + // Create sub_relations map + std::map sub_relations; + sub_relations[1001] = audio_desc.get(); + sub_relations[2001] = video_desc.get(); + + // Initialize play stream + HELPER_EXPECT_SUCCESS(play_stream->initialize(mock_req.get(), sub_relations)); + + // Get references to the created tracks + MockRtspSendTrack *audio_track = dynamic_cast(play_stream->audio_tracks_[1001]); + MockRtspSendTrack *video_track = dynamic_cast(play_stream->video_tracks_[2001]); + EXPECT_TRUE(audio_track != NULL); + EXPECT_TRUE(video_track != NULL); + + // Verify cache is initially empty + EXPECT_EQ(0u, play_stream->cache_ssrc0_); + EXPECT_EQ(0u, play_stream->cache_ssrc1_); + EXPECT_EQ(0u, play_stream->cache_ssrc2_); + EXPECT_TRUE(play_stream->cache_track0_ == NULL); + EXPECT_TRUE(play_stream->cache_track1_ == NULL); + EXPECT_TRUE(play_stream->cache_track2_ == NULL); + + // Create audio RTP packet with SSRC 1001 + SrsUniquePtr audio_pkt(new SrsRtpPacket()); + audio_pkt->header_.set_ssrc(1001); + audio_pkt->header_.set_sequence(100); + audio_pkt->frame_type_ = SrsFrameTypeAudio; + + // Send audio packet - should build cache for slot 0 + SrsRtpPacket *audio_pkt_ptr = audio_pkt.get(); + HELPER_EXPECT_SUCCESS(play_stream->send_packet(audio_pkt_ptr)); + + // Verify audio track received the packet + EXPECT_EQ(1, audio_track->on_rtp_count_); + EXPECT_EQ(1001u, audio_track->last_ssrc_); + EXPECT_EQ(100, audio_track->last_sequence_); + + // Verify cache was built for audio track in slot 0 + EXPECT_EQ(1001u, play_stream->cache_ssrc0_); + EXPECT_EQ(audio_track, play_stream->cache_track0_); + EXPECT_EQ(0u, play_stream->cache_ssrc1_); + EXPECT_EQ(0u, play_stream->cache_ssrc2_); + + // Create video RTP packet with SSRC 2001 + SrsUniquePtr video_pkt(new SrsRtpPacket()); + video_pkt->header_.set_ssrc(2001); + video_pkt->header_.set_sequence(200); + video_pkt->frame_type_ = SrsFrameTypeVideo; + + // Send video packet - should build cache for slot 1 + SrsRtpPacket *video_pkt_ptr = video_pkt.get(); + HELPER_EXPECT_SUCCESS(play_stream->send_packet(video_pkt_ptr)); + + // Verify video track received the packet + EXPECT_EQ(1, video_track->on_rtp_count_); + EXPECT_EQ(2001u, video_track->last_ssrc_); + EXPECT_EQ(200, video_track->last_sequence_); + + // Verify cache was built for video track in slot 1 + EXPECT_EQ(1001u, play_stream->cache_ssrc0_); + EXPECT_EQ(audio_track, play_stream->cache_track0_); + EXPECT_EQ(2001u, play_stream->cache_ssrc1_); + EXPECT_EQ(video_track, play_stream->cache_track1_); + EXPECT_EQ(0u, play_stream->cache_ssrc2_); + + // Send another audio packet - should hit cache slot 0 + audio_track->reset(); + SrsUniquePtr audio_pkt2(new SrsRtpPacket()); + audio_pkt2->header_.set_ssrc(1001); + audio_pkt2->header_.set_sequence(101); + audio_pkt2->frame_type_ = SrsFrameTypeAudio; + + SrsRtpPacket *audio_pkt2_ptr = audio_pkt2.get(); + HELPER_EXPECT_SUCCESS(play_stream->send_packet(audio_pkt2_ptr)); + + // Verify audio track received the second packet + EXPECT_EQ(1, audio_track->on_rtp_count_); + EXPECT_EQ(1001u, audio_track->last_ssrc_); + EXPECT_EQ(101, audio_track->last_sequence_); + + // Send another video packet - should hit cache slot 1 + video_track->reset(); + SrsUniquePtr video_pkt2(new SrsRtpPacket()); + video_pkt2->header_.set_ssrc(2001); + video_pkt2->header_.set_sequence(201); + video_pkt2->frame_type_ = SrsFrameTypeVideo; + + SrsRtpPacket *video_pkt2_ptr = video_pkt2.get(); + HELPER_EXPECT_SUCCESS(play_stream->send_packet(video_pkt2_ptr)); + + // Verify video track received the second packet + EXPECT_EQ(1, video_track->on_rtp_count_); + EXPECT_EQ(2001u, video_track->last_ssrc_); + EXPECT_EQ(201, video_track->last_sequence_); + + // Test packet with unknown SSRC - should be dropped silently + SrsUniquePtr unknown_pkt(new SrsRtpPacket()); + unknown_pkt->header_.set_ssrc(9999); + unknown_pkt->header_.set_sequence(999); + unknown_pkt->frame_type_ = SrsFrameTypeAudio; + + SrsRtpPacket *unknown_pkt_ptr = unknown_pkt.get(); + HELPER_EXPECT_SUCCESS(play_stream->send_packet(unknown_pkt_ptr)); + + // Verify tracks did not receive the unknown packet + EXPECT_EQ(1, audio_track->on_rtp_count_); + EXPECT_EQ(1, video_track->on_rtp_count_); +} + +VOID TEST(RtspPlayStreamTest, SetAllTracksStatus) +{ + // Create mock dependencies + MockRtspConnection mock_session; + MockStatisticForRtspPlayStream mock_stat; + MockRtspSourceManager mock_source_manager; + MockAppFactoryForRtspPlayStream mock_factory; + + // Create SrsRtspPlayStream + SrsContextId cid; + SrsUniquePtr play_stream(new SrsRtspPlayStream(&mock_session, cid)); + + // Inject mock dependencies + play_stream->stat_ = &mock_stat; + play_stream->rtsp_sources_ = &mock_source_manager; + play_stream->app_factory_ = &mock_factory; + + // Create mock track descriptions + SrsUniquePtr audio_desc1(new SrsRtcTrackDescription()); + audio_desc1->type_ = "audio"; + audio_desc1->id_ = "audio-track-1"; + audio_desc1->ssrc_ = 1001; + + SrsUniquePtr audio_desc2(new SrsRtcTrackDescription()); + audio_desc2->type_ = "audio"; + audio_desc2->id_ = "audio-track-2"; + audio_desc2->ssrc_ = 1002; + + SrsUniquePtr video_desc1(new SrsRtcTrackDescription()); + video_desc1->type_ = "video"; + video_desc1->id_ = "video-track-1"; + video_desc1->ssrc_ = 2001; + + SrsUniquePtr video_desc2(new SrsRtcTrackDescription()); + video_desc2->type_ = "video"; + video_desc2->id_ = "video-track-2"; + video_desc2->ssrc_ = 2002; + + // Create mock tracks + MockRtspSendTrack *audio_track1 = new MockRtspSendTrack("audio-track-1", audio_desc1.get()); + MockRtspSendTrack *audio_track2 = new MockRtspSendTrack("audio-track-2", audio_desc2.get()); + MockRtspSendTrack *video_track1 = new MockRtspSendTrack("video-track-1", video_desc1.get()); + MockRtspSendTrack *video_track2 = new MockRtspSendTrack("video-track-2", video_desc2.get()); + + // Add tracks to play_stream + play_stream->audio_tracks_[1001] = audio_track1; + play_stream->audio_tracks_[1002] = audio_track2; + play_stream->video_tracks_[2001] = video_track1; + play_stream->video_tracks_[2002] = video_track2; + + // Verify initial status is false (default from MockRtspSendTrack constructor) + EXPECT_FALSE(audio_track1->track_status_); + EXPECT_FALSE(audio_track2->track_status_); + EXPECT_FALSE(video_track1->track_status_); + EXPECT_FALSE(video_track2->track_status_); + + // Set all tracks to active + play_stream->set_all_tracks_status(true); + + // Verify all tracks are now active + EXPECT_TRUE(audio_track1->track_status_); + EXPECT_TRUE(audio_track2->track_status_); + EXPECT_TRUE(video_track1->track_status_); + EXPECT_TRUE(video_track2->track_status_); + + // Set all tracks to inactive + play_stream->set_all_tracks_status(false); + + // Verify all tracks are now inactive + EXPECT_FALSE(audio_track1->track_status_); + EXPECT_FALSE(audio_track2->track_status_); + EXPECT_FALSE(video_track1->track_status_); + EXPECT_FALSE(video_track2->track_status_); + + // Note: play_stream will be destroyed before mocks go out of scope + // The destructor will free the tracks in the maps and call stat_->on_disconnect() +} + +// Test SrsRtspConnection::on_rtsp_request() - major use scenario +// This test covers the complete RTSP play flow which is the most common scenario: +// 1. Client sends OPTIONS request to query server capabilities +// 2. Client sends DESCRIBE request to get stream SDP information +// 3. Client sends SETUP request to configure transport for a track +// 4. Client sends PLAY request to start streaming +// 5. Client sends TEARDOWN request to stop streaming +// +// This test uses mocks to avoid needing real network connections and RTSP sources. +VOID TEST(RtspConnectionTest, OnRtspRequestCompletePlayFlow) +{ + srs_error_t err; + + // Create mock RTSP stack + MockRtspStack *mock_rtsp = new MockRtspStack(); + + // Create RTSP connection (use minimal constructor parameters) + SrsUniquePtr conn(new SrsRtspConnection(NULL, NULL, "127.0.0.1", 8554)); + + // Inject mock RTSP stack + conn->rtsp_ = mock_rtsp; + + // Initialize session_id for testing + conn->session_id_ = "test_session_123"; + + // Test 1: OPTIONS request + { + SrsRtspRequest *req = new SrsRtspRequest(); + req->method_ = "OPTIONS"; + req->uri_ = "rtsp://127.0.0.1:8554/live/stream"; + req->seq_ = 1; + + mock_rtsp->reset(); + err = conn->on_rtsp_request(req); + HELPER_EXPECT_SUCCESS(err); + + // Verify OPTIONS response was sent + EXPECT_TRUE(mock_rtsp->send_message_called_); + EXPECT_EQ(1, mock_rtsp->last_response_seq_); + EXPECT_STREQ("OPTIONS", mock_rtsp->last_response_type_.c_str()); + } + + // Test 2: DESCRIBE request + { + SrsRtspRequest *req = new SrsRtspRequest(); + req->method_ = "DESCRIBE"; + req->uri_ = "rtsp://127.0.0.1:8554/live/stream"; + req->seq_ = 2; + + mock_rtsp->reset(); + err = conn->on_rtsp_request(req); + HELPER_EXPECT_SUCCESS(err); + + // Verify DESCRIBE response was sent + EXPECT_TRUE(mock_rtsp->send_message_called_); + EXPECT_EQ(2, mock_rtsp->last_response_seq_); + EXPECT_STREQ("DESCRIBE", mock_rtsp->last_response_type_.c_str()); + EXPECT_STREQ("test_session_123", mock_rtsp->last_response_session_.c_str()); + } + + // Test 3: SETUP request + { + SrsRtspRequest *req = new SrsRtspRequest(); + req->method_ = "SETUP"; + req->uri_ = "rtsp://127.0.0.1:8554/live/stream/trackID=0"; + req->seq_ = 3; + req->stream_id_ = 0; + req->transport_ = new SrsRtspTransport(); + req->transport_->transport_ = "RTP"; + req->transport_->profile_ = "AVP"; + req->transport_->lower_transport_ = "UDP"; + req->transport_->client_port_min_ = 50000; + req->transport_->client_port_max_ = 50001; + + mock_rtsp->reset(); + err = conn->on_rtsp_request(req); + HELPER_EXPECT_SUCCESS(err); + + // Verify SETUP response was sent + EXPECT_TRUE(mock_rtsp->send_message_called_); + EXPECT_EQ(3, mock_rtsp->last_response_seq_); + EXPECT_STREQ("SETUP", mock_rtsp->last_response_type_.c_str()); + EXPECT_STREQ("test_session_123", mock_rtsp->last_response_session_.c_str()); + } + + // Test 4: PLAY request + { + SrsRtspRequest *req = new SrsRtspRequest(); + req->method_ = "PLAY"; + req->uri_ = "rtsp://127.0.0.1:8554/live/stream"; + req->seq_ = 4; + req->session_ = "test_session_123"; + + mock_rtsp->reset(); + err = conn->on_rtsp_request(req); + HELPER_EXPECT_SUCCESS(err); + + // Verify PLAY response was sent + EXPECT_TRUE(mock_rtsp->send_message_called_); + EXPECT_EQ(4, mock_rtsp->last_response_seq_); + EXPECT_STREQ("GENERIC", mock_rtsp->last_response_type_.c_str()); + EXPECT_STREQ("test_session_123", mock_rtsp->last_response_session_.c_str()); + } + + // Test 5: TEARDOWN request + { + SrsRtspRequest *req = new SrsRtspRequest(); + req->method_ = "TEARDOWN"; + req->uri_ = "rtsp://127.0.0.1:8554/live/stream"; + req->seq_ = 5; + req->session_ = "test_session_123"; + + mock_rtsp->reset(); + err = conn->on_rtsp_request(req); + HELPER_EXPECT_SUCCESS(err); + + // Verify TEARDOWN response was sent + EXPECT_TRUE(mock_rtsp->send_message_called_); + EXPECT_EQ(5, mock_rtsp->last_response_seq_); + EXPECT_STREQ("GENERIC", mock_rtsp->last_response_type_.c_str()); + EXPECT_STREQ("test_session_123", mock_rtsp->last_response_session_.c_str()); + } + + // Clean up: Set to NULL to avoid double-free + conn->rtsp_ = NULL; + srs_freep(mock_rtsp); +} + +// Test SrsRtspConnection lifecycle and session management - major use scenario +// This test covers the complete lifecycle of an RTSP connection including: +// 1. Session timeout management (is_alive/alive methods) +// 2. Resource disposal lifecycle (on_before_dispose/on_disposing methods) +// 3. Context management (switch_to_context/context_id methods) +// +// This represents the typical lifecycle of an RTSP connection from creation +// through active session management to disposal. +VOID TEST(RtspConnectionTest, SessionLifecycleAndDisposal) +{ + // Create RTSP connection + SrsUniquePtr conn(new SrsRtspConnection(NULL, NULL, "127.0.0.1", 8554)); + + // Test 1: Context management + { + // Get the context ID + const SrsContextId& cid = conn->context_id(); + EXPECT_FALSE(cid.empty()); + + // Switch to context should set the global context + conn->switch_to_context(); + EXPECT_TRUE(_srs_context->get_id().compare(cid) == 0); + } + + // Test 2: Session timeout management + { + // Set session timeout to 5 seconds + conn->session_timeout = 5 * SRS_UTIME_SECONDS; + + // Initially, connection should not be alive (last_stun_time is 0) + EXPECT_FALSE(conn->is_alive()); + + // Mark connection as alive + conn->alive(); + + // Now connection should be alive + EXPECT_TRUE(conn->is_alive()); + + // Simulate time passing (but within timeout) + srs_utime_t original_time = conn->last_stun_time; + conn->last_stun_time = original_time - 3 * SRS_UTIME_SECONDS; + EXPECT_TRUE(conn->is_alive()); + + // Simulate timeout expiration + conn->last_stun_time = original_time - 6 * SRS_UTIME_SECONDS; + EXPECT_FALSE(conn->is_alive()); + + // Refresh the session + conn->alive(); + EXPECT_TRUE(conn->is_alive()); + } + + // Test 3: Resource disposal lifecycle + { + // Initially, disposing flag should be false + EXPECT_FALSE(conn->disposing_); + + // Simulate on_before_dispose being called with this connection + conn->on_before_dispose(conn.get()); + + // After on_before_dispose, disposing flag should be true + EXPECT_TRUE(conn->disposing_); + + // Calling on_before_dispose again should be safe (early return) + conn->on_before_dispose(conn.get()); + EXPECT_TRUE(conn->disposing_); + + // Calling on_disposing should be safe (early return due to disposing_ flag) + conn->on_disposing(conn.get()); + EXPECT_TRUE(conn->disposing_); + } + + // Test 4: on_before_dispose with different resource (not self) + { + // Create another connection to test disposal of different resource + SrsUniquePtr other_conn(new SrsRtspConnection(NULL, NULL, "127.0.0.2", 8555)); + + // Reset disposing flag for testing + conn->disposing_ = false; + + // Call on_before_dispose with a different resource + conn->on_before_dispose(other_conn.get()); + + // disposing_ should remain false (not disposing self) + EXPECT_FALSE(conn->disposing_); + + // Call on_disposing with a different resource + conn->on_disposing(other_conn.get()); + + // disposing_ should still be false + EXPECT_FALSE(conn->disposing_); + } +} + +VOID TEST(RtspConnectionTest, DoDescribeWithAudioAndVideo) +{ + srs_error_t err = srs_success; + + // Create mock objects + MockEdgeConfig mock_config; + MockSecurityForLiveStream mock_security; + MockHttpHooks mock_hooks; + MockRtspSourceManager mock_rtsp_sources; + + // Create a mock RTSP source with audio and video track descriptions + SrsSharedPtr mock_source(new SrsRtspSource()); + + // Create audio track description with AAC codec + SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription(); + audio_desc->type_ = "audio"; + audio_desc->ssrc_ = 1001; + SrsAudioPayload *audio_payload = new SrsAudioPayload(97, "MPEG4-GENERIC", 48000, 2); + audio_payload->aac_config_hex_ = "1190"; // AAC config hex + audio_desc->media_ = audio_payload; + mock_source->audio_desc_ = audio_desc; + + // Create video track description with H264 codec + SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription(); + video_desc->type_ = "video"; + video_desc->ssrc_ = 2001; + video_desc->media_ = new SrsVideoPayload(96, "H264", 90000); + mock_source->video_desc_ = video_desc; + + // Configure mock source manager to return the mock source + mock_rtsp_sources.mock_source_ = mock_source; + mock_rtsp_sources.fetch_or_create_error_ = srs_success; + + // Create RTSP connection + SrsUniquePtr conn(new SrsRtspConnection(NULL, NULL, "127.0.0.1", 8554)); + + // Inject mock dependencies + conn->config_ = &mock_config; + conn->security_ = &mock_security; + conn->hooks_ = &mock_hooks; + conn->rtsp_sources_ = &mock_rtsp_sources; + + // Create RTSP request with URI + SrsUniquePtr req(new SrsRtspRequest()); + req->uri_ = "rtsp://127.0.0.1:8554/live/stream"; + + // Call do_describe + std::string sdp; + HELPER_EXPECT_SUCCESS(conn->do_describe(req.get(), sdp)); + + // Verify SDP is not empty + EXPECT_FALSE(sdp.empty()); + + // Verify SDP contains expected session information + EXPECT_TRUE(sdp.find("v=0") != std::string::npos); + EXPECT_TRUE(sdp.find("s=Play") != std::string::npos); + EXPECT_TRUE(sdp.find("c=IN IP4 0.0.0.0") != std::string::npos); + + // Verify SDP contains audio media description + EXPECT_TRUE(sdp.find("m=audio") != std::string::npos); + EXPECT_TRUE(sdp.find("RTP/AVP") != std::string::npos); + EXPECT_TRUE(sdp.find("MPEG4-GENERIC") != std::string::npos); + EXPECT_TRUE(sdp.find("a=rtpmap:97 MPEG4-GENERIC/48000/2") != std::string::npos); + + // Verify AAC fmtp line is present with config + EXPECT_TRUE(sdp.find("a=fmtp:97") != std::string::npos); + EXPECT_TRUE(sdp.find("config=1190") != std::string::npos); + EXPECT_TRUE(sdp.find("streamtype=5") != std::string::npos); + EXPECT_TRUE(sdp.find("mode=AAC-hbr") != std::string::npos); + + // Verify SDP contains video media description + EXPECT_TRUE(sdp.find("m=video") != std::string::npos); + EXPECT_TRUE(sdp.find("H264") != std::string::npos); + EXPECT_TRUE(sdp.find("a=rtpmap:96 H264/90000") != std::string::npos); + + // Verify control URLs are present + EXPECT_TRUE(sdp.find("a=control:") != std::string::npos); + EXPECT_TRUE(sdp.find("trackID=0") != std::string::npos); + EXPECT_TRUE(sdp.find("trackID=1") != std::string::npos); + + // Verify recvonly attribute + EXPECT_TRUE(sdp.find("a=recvonly") != std::string::npos); + + // Verify tracks were stored in connection + EXPECT_EQ(2, (int)conn->tracks_.size()); + EXPECT_TRUE(conn->tracks_.find(1001) != conn->tracks_.end()); + EXPECT_TRUE(conn->tracks_.find(2001) != conn->tracks_.end()); + + // Clean up injected mocks to avoid double-free + conn->config_ = NULL; + conn->security_ = NULL; + conn->hooks_ = NULL; + conn->rtsp_sources_ = NULL; +} + +// Test SrsRtspConnection::do_play() and do_teardown() - major use scenario +// This test covers the typical RTSP play flow: +// 1. Client sends PLAY request to start streaming +// 2. Server creates SrsRtspPlayStream, initializes it, sets track status, and starts it +// 3. Client sends TEARDOWN request to stop streaming +// 4. Server stops and frees the player +// +// Note: In production code, SrsRtspConnection creates a real SrsRtspPlayStream. +// However, we cannot easily mock the player creation since it's done with 'new' inside do_play(). +// Therefore, this test verifies the flow by checking that a real player is created and properly managed. +VOID TEST(RtspConnectionTest, DoPlayAndTeardown) +{ + srs_error_t err = srs_success; + + // Create RTSP connection + SrsUniquePtr conn(new SrsRtspConnection(NULL, NULL, "127.0.0.1", 8554)); + + // Create mock dependencies + SrsUniquePtr mock_config(new MockEdgeConfig()); + SrsUniquePtr mock_stat(new MockStatisticForRtspPlayStream()); + SrsUniquePtr mock_rtsp_sources(new MockRtspSourceManager()); + + // Inject mock dependencies into connection + conn->config_ = mock_config.get(); + conn->stat_ = mock_stat.get(); + conn->rtsp_sources_ = mock_rtsp_sources.get(); + + // Initialize request with stream information + conn->request_->vhost_ = "test.vhost"; + conn->request_->app_ = "live"; + conn->request_->stream_ = "stream1"; + + // Create track descriptions for testing + SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription(); + audio_desc->type_ = "audio"; + audio_desc->id_ = "0"; + audio_desc->ssrc_ = 1001; + conn->tracks_[1001] = audio_desc; + + SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription(); + video_desc->type_ = "video"; + video_desc->id_ = "1"; + video_desc->ssrc_ = 2001; + conn->tracks_[2001] = video_desc; + + // Create RTSP PLAY request + SrsUniquePtr play_req(new SrsRtspRequest()); + play_req->method_ = "PLAY"; + play_req->uri_ = "rtsp://127.0.0.1:8554/live/stream1"; + play_req->seq_ = 4; + + // Test do_play: This will create a real SrsRtspPlayStream + // We verify that the player is created and the flow completes successfully + HELPER_EXPECT_SUCCESS(conn->do_play(play_req.get(), conn.get())); + + // Verify player was created + EXPECT_TRUE(conn->player_ != NULL); + + // Test do_teardown: This should stop and free the player + HELPER_EXPECT_SUCCESS(conn->do_teardown()); + + // Verify player was freed + EXPECT_TRUE(conn->player_ == NULL); + + // Clean up injected mocks to avoid double-free + conn->config_ = NULL; + conn->stat_ = NULL; + conn->rtsp_sources_ = NULL; +} + +// Test SrsRtspConnection::do_setup() - major use scenario +// This test covers the successful SETUP request with TCP transport which is the most common scenario: +// 1. Client sends SETUP request with TCP/interleaved transport +// 2. Server looks up track by stream_id to get SSRC +// 3. Server creates SrsRtspTcpNetwork for the track +// 4. Server returns the SSRC to caller +VOID TEST(RtspConnectionTest, DoSetupWithTcpTransport) +{ + srs_error_t err = srs_success; + + // Create RTSP connection + SrsUniquePtr conn(new SrsRtspConnection(NULL, NULL, "127.0.0.1", 8554)); + + // Create a track description with known stream_id and ssrc + SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription(); + video_desc->type_ = "video"; + video_desc->id_ = "0"; // stream_id will be 0 + video_desc->ssrc_ = 12345; + + // Add track to connection's tracks map + conn->tracks_[12345] = video_desc; + + // Create RTSP SETUP request with TCP transport + SrsUniquePtr req(new SrsRtspRequest()); + req->method_ = "SETUP"; + req->stream_id_ = 0; // Matches track id_ = "0" + + // Configure TCP transport (interleaved mode) + req->transport_ = new SrsRtspTransport(); + req->transport_->transport_ = "RTP"; + req->transport_->profile_ = "AVP"; + req->transport_->lower_transport_ = "TCP"; + req->transport_->interleaved_min_ = 0; + req->transport_->interleaved_max_ = 1; + + // Call do_setup + uint32_t ssrc = 0; + HELPER_EXPECT_SUCCESS(conn->do_setup(req.get(), &ssrc)); + + // Verify SSRC was returned correctly + EXPECT_EQ(12345, (int)ssrc); + + // Verify network was created for this SSRC + EXPECT_EQ(1, (int)conn->networks_.size()); + EXPECT_TRUE(conn->networks_.find(12345) != conn->networks_.end()); + + // Clean up: Free the network object + ISrsStreamWriter *network = conn->networks_[12345]; + srs_freep(network); + conn->networks_.clear(); +} + +// Test SrsRtspConnection::http_hooks_on_play() to verify HTTP hooks are called correctly +// when playing RTSP streams. This covers the major use scenario where HTTP hooks are enabled +// and multiple hook URLs are configured for on_play events. +VOID TEST(SrsRtspConnectionTest, HttpHooksOnPlaySuccess) +{ + srs_error_t err = srs_success; + + // Create mock request + SrsUniquePtr mock_request(new MockEdgeRequest("test.vhost", "live", "stream1")); + + // Create RTSP connection (we don't need real transport for this test) + SrsUniquePtr conn(new SrsRtspConnection(NULL, NULL, "127.0.0.1", 554)); + + // Create mock config with HTTP hooks enabled + MockAppConfigForHttpHooksOnPlay *mock_config = new MockAppConfigForHttpHooksOnPlay(); + mock_config->http_hooks_enabled_ = true; + + // Create on_play directive with two hook URLs + mock_config->on_play_directive_ = new SrsConfDirective(); + mock_config->on_play_directive_->name_ = "on_play"; + mock_config->on_play_directive_->args_.push_back("http://127.0.0.1:8085/api/v1/rtsp/play"); + mock_config->on_play_directive_->args_.push_back("http://localhost:8085/api/v1/rtsp/play"); + + // Create mock hooks + MockHttpHooksForOnPlay *mock_hooks = new MockHttpHooksForOnPlay(); + + // Inject mocks into connection + conn->config_ = mock_config; + conn->hooks_ = mock_hooks; + + // Test the major use scenario: http_hooks_on_play() with hooks enabled + // This should: + // 1. Check if HTTP hooks are enabled (they are) + // 2. Get the on_play directive from config + // 3. Copy the hook URLs from the directive + // 4. Call hooks_->on_play() for each URL + HELPER_EXPECT_SUCCESS(conn->http_hooks_on_play(mock_request.get())); + + // Verify that on_play was called twice (once for each URL) + EXPECT_EQ(2, mock_hooks->on_play_count_); + EXPECT_EQ(2, (int)mock_hooks->on_play_calls_.size()); + + // Verify the first call + EXPECT_STREQ("http://127.0.0.1:8085/api/v1/rtsp/play", mock_hooks->on_play_calls_[0].first.c_str()); + EXPECT_TRUE(mock_hooks->on_play_calls_[0].second == mock_request.get()); + + // Verify the second call + EXPECT_STREQ("http://localhost:8085/api/v1/rtsp/play", mock_hooks->on_play_calls_[1].first.c_str()); + EXPECT_TRUE(mock_hooks->on_play_calls_[1].second == mock_request.get()); + + // Cleanup: restore to NULL to avoid accessing freed memory + conn->config_ = NULL; + conn->hooks_ = NULL; + srs_freep(mock_config); + srs_freep(mock_hooks); +} + +// Test SrsRtspConnection::get_ssrc_by_stream_id() - major use scenario +// This test covers the typical scenario where RTSP SETUP request needs to map +// stream_id to SSRC to identify which track the client wants to setup. +// The function searches through tracks_ map to find a track whose id_ matches +// the stream_id (converted to string), then returns the corresponding SSRC. +VOID TEST(RtspConnectionTest, GetSsrcByStreamIdSuccess) +{ + srs_error_t err = srs_success; + + // Create RTSP connection + SrsUniquePtr conn(new SrsRtspConnection(NULL, NULL, "127.0.0.1", 8554)); + + // Create multiple track descriptions with different stream IDs + SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription(); + audio_desc->type_ = "audio"; + audio_desc->id_ = "0"; // stream_id 0 + audio_desc->ssrc_ = 1001; + + SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription(); + video_desc->type_ = "video"; + video_desc->id_ = "1"; // stream_id 1 + video_desc->ssrc_ = 2001; + + // Add tracks to connection's tracks map (key is SSRC) + conn->tracks_[1001] = audio_desc; + conn->tracks_[2001] = video_desc; + + // Test successful lookup for audio track (stream_id 0) + uint32_t ssrc = 0; + HELPER_EXPECT_SUCCESS(conn->get_ssrc_by_stream_id(0, &ssrc)); + EXPECT_EQ(1001, (int)ssrc); + + // Test successful lookup for video track (stream_id 1) + ssrc = 0; + HELPER_EXPECT_SUCCESS(conn->get_ssrc_by_stream_id(1, &ssrc)); + EXPECT_EQ(2001, (int)ssrc); + + // Test failure case: stream_id not found + HELPER_EXPECT_FAILED(conn->get_ssrc_by_stream_id(999, &ssrc)); +} + +VOID TEST(RtspTcpNetworkTest, WriteRtpPacket) +{ + srs_error_t err; + + // Create mock socket with buffer + SrsUniquePtr mock_skt(new MockBufferIO()); + + // Create SrsRtspTcpNetwork with channel 0 + int channel = 0; + SrsUniquePtr tcp_network(new SrsRtspTcpNetwork(mock_skt.get(), channel)); + + // Prepare test RTP packet data + const int kRtpPacketSize = 100; + char rtp_packet[kRtpPacketSize]; + memset(rtp_packet, 0xAB, kRtpPacketSize); + + // Write RTP packet + ssize_t nwrite = 0; + HELPER_EXPECT_SUCCESS(tcp_network->write(rtp_packet, kRtpPacketSize, &nwrite)); + + // Verify total bytes written (4 byte header + 100 byte payload) + EXPECT_EQ(104, (int)nwrite); + + // Verify the output buffer contains correct data + EXPECT_EQ(104, mock_skt->out_length()); + + // Get the output buffer data + char *output = mock_skt->out_buffer.bytes(); + ASSERT_TRUE(output != NULL); + + // Verify magic byte '$' (0x24) + EXPECT_EQ(0x24, (uint8_t)output[0]); + + // Verify channel number + EXPECT_EQ(0, (uint8_t)output[1]); + + // Verify packet size in network order (big-endian) + uint16_t size = ((uint8_t)output[2] << 8) | (uint8_t)output[3]; + EXPECT_EQ(100, (int)size); + + // Verify the payload data (starts at offset 4) + EXPECT_EQ(0, memcmp(rtp_packet, output + 4, kRtpPacketSize)); +} diff --git a/trunk/src/utest/srs_utest_app13.hpp b/trunk/src/utest/srs_utest_app13.hpp new file mode 100644 index 000000000..38a8d691d --- /dev/null +++ b/trunk/src/utest/srs_utest_app13.hpp @@ -0,0 +1,481 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_UTEST_APP13_HPP +#define SRS_UTEST_APP13_HPP + +/* +#include +*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Mock request class for testing edge upstream +class MockEdgeRequest : public ISrsRequest +{ +public: + MockEdgeRequest(std::string vhost = "__defaultVhost__", std::string app = "live", std::string stream = "test"); + virtual ~MockEdgeRequest(); + virtual ISrsRequest *copy(); + virtual std::string get_stream_url(); + virtual void update_auth(ISrsRequest *req); + virtual void strip(); + virtual ISrsRequest *as_http(); +}; + +// Mock config class for testing edge upstream +class MockEdgeConfig : public MockAppConfig +{ +public: + SrsConfDirective *edge_origin_directive_; + std::string edge_transform_vhost_; + int chunk_size_; + +public: + MockEdgeConfig(); + virtual ~MockEdgeConfig(); + void reset(); + +public: + // Override methods needed for edge upstream testing + virtual SrsConfDirective *get_vhost_edge_origin(std::string vhost); + virtual std::string get_vhost_edge_transform_vhost(std::string vhost); + virtual int get_chunk_size(std::string vhost); + virtual srs_utime_t get_vhost_edge_origin_connect_timeout(std::string vhost); + virtual srs_utime_t get_vhost_edge_origin_stream_timeout(std::string vhost); +}; + +// Mock RTMP client for testing edge upstream +class MockEdgeRtmpClient : public ISrsBasicRtmpClient +{ +public: + bool connect_called_; + bool play_called_; + bool close_called_; + bool recv_message_called_; + bool decode_message_called_; + bool set_recv_timeout_called_; + bool kbps_sample_called_; + srs_error_t connect_error_; + srs_error_t play_error_; + std::string play_stream_; + srs_utime_t recv_timeout_; + std::string kbps_label_; + srs_utime_t kbps_age_; + +public: + MockEdgeRtmpClient(); + virtual ~MockEdgeRtmpClient(); + +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); +}; + +// Mock app factory for testing edge upstream +class MockEdgeAppFactory : public SrsAppFactory +{ +public: + MockEdgeRtmpClient *mock_client_; + +public: + MockEdgeAppFactory(); + virtual ~MockEdgeAppFactory(); + +public: + virtual ISrsBasicRtmpClient *create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto); +}; + +// Mock HTTP client for testing edge FLV upstream +class MockEdgeHttpClient : public ISrsHttpClient +{ +public: + bool initialize_called_; + bool get_called_; + bool set_recv_timeout_called_; + bool kbps_sample_called_; + srs_error_t initialize_error_; + srs_error_t get_error_; + ISrsHttpMessage *mock_response_; + std::string schema_; + std::string host_; + int port_; + std::string path_; + std::string kbps_label_; + srs_utime_t kbps_age_; + +public: + MockEdgeHttpClient(); + virtual ~MockEdgeHttpClient(); + +public: + virtual srs_error_t initialize(std::string schema, std::string h, int p, srs_utime_t tm); + virtual srs_error_t get(std::string path, std::string req, ISrsHttpMessage **ppmsg); + virtual srs_error_t post(std::string path, std::string req, ISrsHttpMessage **ppmsg); + virtual void set_recv_timeout(srs_utime_t tm); + virtual void kbps_sample(const char *label, srs_utime_t age); +}; + +// Mock HTTP message for testing edge FLV upstream +class MockEdgeHttpMessage : public ISrsHttpMessage +{ +public: + int status_code_; + SrsHttpHeader *header_; + ISrsHttpResponseReader *body_reader_; + +public: + MockEdgeHttpMessage(); + virtual ~MockEdgeHttpMessage(); + +public: + virtual uint8_t message_type(); + virtual uint8_t method(); + virtual uint16_t status_code(); + virtual std::string method_str(); + virtual bool is_http_get(); + virtual bool is_http_put(); + virtual bool is_http_post(); + virtual bool is_http_delete(); + virtual bool is_http_options(); + virtual std::string uri(); + virtual std::string url(); + virtual std::string host(); + virtual std::string path(); + virtual std::string query(); + virtual std::string ext(); + virtual srs_error_t body_read_all(std::string &body); + virtual ISrsHttpResponseReader *body_reader(); + virtual int64_t content_length(); + virtual std::string query_get(std::string key); + virtual SrsHttpHeader *header(); + virtual bool is_jsonp(); + virtual bool is_keep_alive(); + virtual std::string parse_rest_id(std::string pattern); +}; + +// Mock file reader for testing edge FLV upstream +class MockEdgeFileReader : public ISrsFileReader +{ +public: + char *data_; + int size_; + int pos_; + +public: + MockEdgeFileReader(const char *data, int size); + virtual ~MockEdgeFileReader(); + +public: + virtual srs_error_t open(std::string p); + virtual void close(); + virtual bool is_open(); + virtual int64_t tellg(); + virtual void skip(int64_t size); + virtual int64_t seek2(int64_t offset); + virtual int64_t filesize(); + virtual srs_error_t read(void *buf, size_t count, ssize_t *pnread); + virtual srs_error_t lseek(off_t offset, int whence, off_t *seeked); +}; + +// Mock FLV decoder for testing edge FLV upstream +class MockEdgeFlvDecoder : public ISrsFlvDecoder +{ +public: + bool initialize_called_; + bool read_header_called_; + bool read_previous_tag_size_called_; + +public: + MockEdgeFlvDecoder(); + virtual ~MockEdgeFlvDecoder(); + +public: + virtual srs_error_t initialize(ISrsReader *fr); + virtual srs_error_t read_header(char header[9]); + virtual srs_error_t read_tag_header(char *ptype, int32_t *pdata_size, uint32_t *ptime); + virtual srs_error_t read_tag_data(char *data, int32_t size); + virtual srs_error_t read_previous_tag_size(char previous_tag_size[4]); +}; + +// Mock app factory for testing edge FLV upstream +class MockEdgeFlvAppFactory : public SrsAppFactory +{ +public: + MockEdgeHttpClient *mock_http_client_; + MockEdgeFileReader *mock_file_reader_; + MockEdgeFlvDecoder *mock_flv_decoder_; + +public: + MockEdgeFlvAppFactory(); + virtual ~MockEdgeFlvAppFactory(); + +public: + virtual ISrsHttpClient *create_http_client(); + virtual ISrsFileReader *create_http_file_reader(ISrsHttpResponseReader *r); + virtual ISrsFlvDecoder *create_flv_decoder(); +}; + +// Mock play edge for testing SrsEdgeIngester +class MockPlayEdge : public ISrsPlayEdge +{ +public: + int on_ingest_play_count_; + srs_error_t on_ingest_play_error_; + +public: + MockPlayEdge(); + virtual ~MockPlayEdge(); + +public: + virtual srs_error_t on_ingest_play(); + void reset(); +}; + +// Mock edge upstream for testing SrsEdgeIngester::process_publish_message +class MockEdgeUpstreamForIngester : public ISrsEdgeUpstream +{ +public: + bool decode_message_called_; + SrsRtmpCommand *decode_message_packet_; + srs_error_t decode_message_error_; + +public: + MockEdgeUpstreamForIngester(); + virtual ~MockEdgeUpstreamForIngester(); + +public: + virtual srs_error_t connect(ISrsRequest *r, ISrsLbRoundRobin *lb); + virtual srs_error_t recv_message(SrsRtmpCommonMessage **pmsg); + virtual srs_error_t decode_message(SrsRtmpCommonMessage *msg, SrsRtmpCommand **ppacket); + virtual void close(); + virtual void selected(std::string &server, int &port); + virtual void set_recv_timeout(srs_utime_t tm); + virtual void kbps_sample(const char *label, srs_utime_t age); + void reset(); +}; + +// Mock publish edge for testing SrsEdgeForwarder +class MockPublishEdge : public ISrsPublishEdge +{ +public: + MockPublishEdge(); + virtual ~MockPublishEdge(); +}; + +// Mock edge ingester for testing SrsPlayEdge +class MockEdgeIngester : public ISrsEdgeIngester +{ +public: + bool initialize_called_; + bool start_called_; + bool stop_called_; + srs_error_t initialize_error_; + srs_error_t start_error_; + +public: + MockEdgeIngester(); + virtual ~MockEdgeIngester(); + +public: + virtual srs_error_t initialize(SrsSharedPtr s, ISrsPlayEdge *e, ISrsRequest *r); + virtual srs_error_t start(); + virtual void stop(); + void reset(); +}; + +// Mock edge forwarder for testing SrsPublishEdge +class MockEdgeForwarder : public ISrsEdgeForwarder +{ +public: + bool initialize_called_; + bool start_called_; + bool stop_called_; + bool set_queue_size_called_; + bool proxy_called_; + srs_utime_t queue_size_; + srs_error_t initialize_error_; + srs_error_t start_error_; + srs_error_t proxy_error_; + +public: + MockEdgeForwarder(); + virtual ~MockEdgeForwarder(); + +public: + virtual void set_queue_size(srs_utime_t queue_size); + virtual srs_error_t initialize(SrsSharedPtr s, ISrsPublishEdge *e, ISrsRequest *r); + virtual srs_error_t start(); + virtual void stop(); + virtual srs_error_t proxy(SrsRtmpCommonMessage *msg); + void reset(); +}; + +// Mock ISrsStatistic for testing SrsRtspPlayStream +class MockStatisticForRtspPlayStream : public ISrsStatistic +{ +public: + int on_client_count_; + srs_error_t on_client_error_; + +public: + MockStatisticForRtspPlayStream(); + virtual ~MockStatisticForRtspPlayStream(); + +public: + virtual void on_disconnect(std::string id, srs_error_t err); + virtual srs_error_t on_client(std::string id, ISrsRequest *req, ISrsExpire *conn, SrsRtmpConnType type); + virtual srs_error_t on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height); + virtual srs_error_t on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, SrsAudioChannels asound_type, SrsAacObjectType aac_object); + virtual void on_stream_publish(ISrsRequest *req, std::string publisher_id); + virtual void on_stream_close(ISrsRequest *req); + virtual void kbps_add_delta(std::string id, ISrsKbpsDelta *delta); + virtual void kbps_sample(); + virtual srs_error_t on_video_frames(ISrsRequest *req, int nb_frames); + virtual std::string server_id(); + virtual std::string service_id(); + virtual std::string service_pid(); + virtual SrsStatisticVhost *find_vhost_by_id(std::string vid); + virtual SrsStatisticStream *find_stream(std::string sid); + virtual SrsStatisticClient *find_client(std::string client_id); + virtual srs_error_t dumps_vhosts(SrsJsonArray *arr); + virtual srs_error_t dumps_streams(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_clients(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs); + void reset(); +}; + +// Mock ISrsRtspSourceManager for testing SrsRtspPlayStream +class MockRtspSourceManager : public ISrsRtspSourceManager +{ +public: + int fetch_or_create_count_; + srs_error_t fetch_or_create_error_; + SrsSharedPtr mock_source_; + +public: + MockRtspSourceManager(); + virtual ~MockRtspSourceManager(); + +public: + virtual srs_error_t initialize(); + virtual srs_error_t fetch_or_create(ISrsRequest *r, SrsSharedPtr &pps); + virtual SrsSharedPtr fetch(ISrsRequest *r); + void reset(); +}; + +// Mock ISrsRtspSendTrack for testing SrsRtspPlayStream +class MockRtspSendTrack : public ISrsRtspSendTrack +{ +public: + std::string track_id_; + SrsRtcTrackDescription *track_desc_; + bool track_status_; + int on_rtp_count_; + uint32_t last_ssrc_; + uint16_t last_sequence_; + +public: + MockRtspSendTrack(std::string track_id, SrsRtcTrackDescription *desc); + virtual ~MockRtspSendTrack(); + +public: + virtual bool set_track_status(bool active); + virtual std::string get_track_id(); + virtual SrsRtcTrackDescription *track_desc(); + virtual srs_error_t on_rtp(SrsRtpPacket *pkt); + void reset(); +}; + +// Mock ISrsAppFactory for testing SrsRtspPlayStream +class MockAppFactoryForRtspPlayStream : public SrsAppFactory +{ +public: + int create_rtsp_audio_send_track_count_; + int create_rtsp_video_send_track_count_; + +public: + MockAppFactoryForRtspPlayStream(); + virtual ~MockAppFactoryForRtspPlayStream(); + +public: + virtual ISrsRtspSendTrack *create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc); + virtual ISrsRtspSendTrack *create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc); + void reset(); +}; + +// Mock ISrsRtspStack for testing SrsRtspConnection +class MockRtspStack : public ISrsRtspStack +{ +public: + bool send_message_called_; + int last_response_seq_; + std::string last_response_session_; + std::string last_response_type_; // "OPTIONS", "DESCRIBE", "SETUP", "PLAY", "TEARDOWN" + srs_error_t send_message_error_; + +public: + MockRtspStack(); + virtual ~MockRtspStack(); + +public: + virtual srs_error_t recv_message(SrsRtspRequest **preq); + virtual srs_error_t send_message(SrsRtspResponse *res); + void reset(); +}; + +// Mock ISrsRtspPlayStream for testing SrsRtspConnection::do_play and do_teardown +class MockRtspPlayStream : public ISrsRtspPlayStream +{ +public: + bool initialize_called_; + bool start_called_; + bool stop_called_; + bool set_all_tracks_status_called_; + bool set_all_tracks_status_value_; + srs_error_t initialize_error_; + srs_error_t start_error_; + +public: + MockRtspPlayStream(); + virtual ~MockRtspPlayStream(); + +public: + virtual srs_error_t initialize(ISrsRequest *request, std::map sub_relations); + virtual srs_error_t start(); + virtual void stop(); + virtual void set_all_tracks_status(bool status); + void reset(); +}; + +#endif diff --git a/trunk/src/utest/srs_utest_app6.hpp b/trunk/src/utest/srs_utest_app6.hpp index 916211a69..a3276d048 100644 --- a/trunk/src/utest/srs_utest_app6.hpp +++ b/trunk/src/utest/srs_utest_app6.hpp @@ -400,6 +400,9 @@ public: virtual bool get_vhost_http_remux_has_video(std::string vhost) { return true; } virtual bool get_vhost_http_remux_guess_has_av(std::string vhost) { return true; } virtual std::string get_vhost_http_remux_mount(std::string vhost) { return ""; } + virtual std::string get_vhost_edge_protocol(std::string vhost) { return "rtmp"; } + virtual bool get_vhost_edge_follow_client(std::string vhost) { return false; } + virtual std::string get_vhost_edge_transform_vhost(std::string vhost) { return ""; } void set_http_hooks_enabled(bool enabled); void set_on_stop_urls(const std::vector &urls); void clear_on_stop_directive(); diff --git a/trunk/src/utest/srs_utest_app9.cpp b/trunk/src/utest/srs_utest_app9.cpp index 231b79539..6890266e2 100644 --- a/trunk/src/utest/srs_utest_app9.cpp +++ b/trunk/src/utest/srs_utest_app9.cpp @@ -1580,6 +1580,40 @@ SrsRtmpFormat *MockLiveSourceForOriginHub::format() return format_; } +srs_error_t MockLiveSourceForOriginHub::on_source_id_changed(SrsContextId id) +{ + return srs_success; +} + +srs_error_t MockLiveSourceForOriginHub::on_publish() +{ + return srs_success; +} + +void MockLiveSourceForOriginHub::on_unpublish() +{ +} + +srs_error_t MockLiveSourceForOriginHub::on_audio(SrsRtmpCommonMessage *audio) +{ + return srs_success; +} + +srs_error_t MockLiveSourceForOriginHub::on_video(SrsRtmpCommonMessage *video) +{ + return srs_success; +} + +srs_error_t MockLiveSourceForOriginHub::on_aggregate(SrsRtmpCommonMessage *msg) +{ + return srs_success; +} + +srs_error_t MockLiveSourceForOriginHub::on_meta_data(SrsRtmpCommonMessage *msg, SrsOnMetaDataPacket *metadata) +{ + return srs_success; +} + // Unit test for SrsOriginHub::initialize typical scenario VOID TEST(AppOriginHubTest, InitializeTypicalScenario) { diff --git a/trunk/src/utest/srs_utest_app9.hpp b/trunk/src/utest/srs_utest_app9.hpp index 7a6950685..e5665fe60 100644 --- a/trunk/src/utest/srs_utest_app9.hpp +++ b/trunk/src/utest/srs_utest_app9.hpp @@ -181,6 +181,13 @@ public: virtual SrsContextId pre_source_id(); virtual SrsMetaCache *meta(); virtual SrsRtmpFormat *format(); + virtual srs_error_t on_source_id_changed(SrsContextId id); + virtual srs_error_t on_publish(); + virtual void on_unpublish(); + virtual srs_error_t on_audio(SrsRtmpCommonMessage *audio); + virtual srs_error_t on_video(SrsRtmpCommonMessage *video); + virtual srs_error_t on_aggregate(SrsRtmpCommonMessage *msg); + virtual srs_error_t on_meta_data(SrsRtmpCommonMessage *msg, SrsOnMetaDataPacket *metadata); }; // Mock ISrsStatistic for testing SrsOriginHub::on_video @@ -297,7 +304,7 @@ public: virtual void untick(int event); }; -// Mock SrsAppFactory for testing SrsLiveSourceManager::fetch_or_create +// Mock ISrsAppFactory for testing SrsLiveSourceManager::fetch_or_create class MockAppFactoryForSourceManager : public SrsAppFactory { public: @@ -331,7 +338,7 @@ public: virtual void on_unpublish(); }; -// Mock SrsAppFactory for testing SrsLiveSource::initialize +// Mock ISrsAppFactory for testing SrsLiveSource::initialize class MockAppFactoryForLiveSource : public SrsAppFactory { public: