From 702a58df6aaf879f3ba42be94622cd645fb06b22 Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Thu, 2 Oct 2025 07:43:54 -0400 Subject: [PATCH] AI: Improve coverage for app rtmp module. --- .augment-guidelines | 65 + trunk/configure | 4 +- trunk/src/app/srs_app_caster_flv.cpp | 5 +- trunk/src/app/srs_app_config.cpp | 12 +- trunk/src/app/srs_app_config.hpp | 94 + trunk/src/app/srs_app_coworkers.cpp | 3 +- trunk/src/app/srs_app_dash.cpp | 28 +- trunk/src/app/srs_app_dash.hpp | 22 +- trunk/src/app/srs_app_dvr.cpp | 11 +- trunk/src/app/srs_app_dvr.hpp | 18 +- trunk/src/app/srs_app_encoder.cpp | 8 + trunk/src/app/srs_app_encoder.hpp | 17 +- trunk/src/app/srs_app_factory.cpp | 15 +- trunk/src/app/srs_app_factory.hpp | 4 + trunk/src/app/srs_app_forward.cpp | 8 + trunk/src/app/srs_app_forward.hpp | 19 +- trunk/src/app/srs_app_fragment.cpp | 15 +- trunk/src/app/srs_app_hds.cpp | 14 +- trunk/src/app/srs_app_hds.hpp | 16 +- trunk/src/app/srs_app_heartbeat.cpp | 3 +- trunk/src/app/srs_app_hls.cpp | 48 +- trunk/src/app/srs_app_hls.hpp | 23 +- trunk/src/app/srs_app_http_api.cpp | 3 +- trunk/src/app/srs_app_http_hooks.cpp | 3 +- trunk/src/app/srs_app_http_static.cpp | 6 +- trunk/src/app/srs_app_http_stream.cpp | 8 +- trunk/src/app/srs_app_ingest.cpp | 3 +- trunk/src/app/srs_app_latest_version.cpp | 9 +- trunk/src/app/srs_app_ng_exec.cpp | 8 + trunk/src/app/srs_app_ng_exec.hpp | 15 +- trunk/src/app/srs_app_process.cpp | 3 +- trunk/src/app/srs_app_rtc_conn.cpp | 6 +- trunk/src/app/srs_app_rtc_dtls.cpp | 3 +- trunk/src/app/srs_app_rtc_server.cpp | 10 +- trunk/src/app/srs_app_rtc_source.cpp | 8 +- trunk/src/app/srs_app_rtc_source.hpp | 4 +- trunk/src/app/srs_app_rtmp_conn.cpp | 3 +- trunk/src/app/srs_app_rtmp_source.cpp | 198 +- trunk/src/app/srs_app_rtmp_source.hpp | 110 +- trunk/src/app/srs_app_rtsp_conn.cpp | 3 +- trunk/src/app/srs_app_rtsp_source.cpp | 8 +- trunk/src/app/srs_app_rtsp_source.hpp | 4 +- trunk/src/app/srs_app_server.cpp | 162 +- trunk/src/app/srs_app_server.hpp | 11 +- trunk/src/app/srs_app_srt_source.hpp | 4 +- trunk/src/app/srs_app_statistic.cpp | 6 +- trunk/src/app/srs_app_statistic.hpp | 5 + trunk/src/app/srs_app_utility.cpp | 17 +- trunk/src/app/srs_app_utility.hpp | 19 +- trunk/src/kernel/srs_kernel_error.hpp | 2 +- trunk/src/kernel/srs_kernel_hourglass.cpp | 10 +- trunk/src/kernel/srs_kernel_hourglass.hpp | 25 +- trunk/src/kernel/srs_kernel_utility.cpp | 78 +- trunk/src/kernel/srs_kernel_utility.hpp | 49 +- trunk/src/protocol/srs_protocol_http_conn.cpp | 13 +- trunk/src/protocol/srs_protocol_http_conn.hpp | 3 + .../src/protocol/srs_protocol_http_stack.cpp | 16 +- .../src/protocol/srs_protocol_http_stack.hpp | 10 +- trunk/src/protocol/srs_protocol_log.cpp | 3 +- trunk/src/protocol/srs_protocol_rtmp_conn.cpp | 3 +- .../protocol/srs_protocol_rtmp_handshake.cpp | 22 +- .../protocol/srs_protocol_rtmp_handshake.hpp | 10 + .../src/protocol/srs_protocol_rtmp_stack.cpp | 15 +- .../src/protocol/srs_protocol_rtmp_stack.hpp | 7 + .../src/protocol/srs_protocol_rtsp_stack.cpp | 3 +- trunk/src/protocol/srs_protocol_utility.cpp | 53 +- trunk/src/protocol/srs_protocol_utility.hpp | 47 +- trunk/src/utest/srs_utest.cpp | 27 +- trunk/src/utest/srs_utest_app6.cpp | 90 + trunk/src/utest/srs_utest_app6.hpp | 63 + trunk/src/utest/srs_utest_app8.cpp | 69 +- trunk/src/utest/srs_utest_app9.cpp | 3310 +++++++++++++++++ trunk/src/utest/srs_utest_app9.hpp | 333 ++ trunk/src/utest/srs_utest_http.cpp | 56 +- trunk/src/utest/srs_utest_http.hpp | 22 + trunk/src/utest/srs_utest_kernel.cpp | 40 +- trunk/src/utest/srs_utest_kernel3.cpp | 15 +- trunk/src/utest/srs_utest_kernel3.hpp | 2 +- trunk/src/utest/srs_utest_protocol2.cpp | 12 +- trunk/src/utest/srs_utest_service.cpp | 36 +- trunk/src/utest/srs_utest_srt.cpp | 6 +- trunk/src/utest/srs_utest_st.cpp | 12 +- 82 files changed, 5045 insertions(+), 508 deletions(-) create mode 100644 trunk/src/utest/srs_utest_app9.cpp create mode 100644 trunk/src/utest/srs_utest_app9.hpp diff --git a/.augment-guidelines b/.augment-guidelines index 815a598ef..b2138d99a 100644 --- a/.augment-guidelines +++ b/.augment-guidelines @@ -363,6 +363,71 @@ code_patterns: - pattern: "#ifdef SRS_VALGRIND" description: "Valgrind support" + dependency_injection_for_testing: + - pattern: "assemble() method pattern" + description: "MANDATORY pattern for making classes testable by separating construction from initialization - applies ONLY to production code, NOT to unit test code or mock classes" + purpose: "Enables dependency injection for unit testing by deferring subscription/registration operations that depend on global state or external dependencies" + + usage: | + WRONG: Direct dependency in constructor (not testable) + class SrsOriginHub { + public: + SrsOriginHub() { + _srs_config->subscribe(this); // Hard dependency on global config + } + ~SrsOriginHub() { + _srs_config->unsubscribe(this); + } + }; + + CORRECT: Separate construction from initialization with assemble() + class SrsOriginHub { + private: + SrsConfig* config_; + public: + SrsOriginHub() { + config_ = _srs_config; // Store reference only + } + void assemble() { + config_->subscribe(this); // Actual initialization + } + ~SrsOriginHub() { + config_->unsubscribe(this); + config_ = NULL; + } + }; + + // Production code usage + hub_ = new SrsOriginHub(); + hub_->assemble(); // Call immediately after construction + + // Unit test usage + SrsOriginHub* hub = new SrsOriginHub(); + hub->config_ = mock_config; // Inject mock dependency + hub->assemble(); // Now uses mock config + // Or skip assemble() entirely to avoid side effects + + scope: "Applies ONLY to production code in trunk/src/app/, trunk/src/protocol/, trunk/src/kernel/, trunk/src/core/ - does NOT apply to unit test code or mock classes" + + when_to_use: + - "When constructor needs to invoke functions (e.g., subscribe(), register(), initialize() on dependencies)" + - "Exception: Simple object creation functions like srs_mutex_new() can remain in constructor - no need to extract to assemble()" + - "Rule: If constructor calls methods on dependencies, move those calls to assemble()" + + when_not_to_use: + - "In unit test code (trunk/src/utest/) - tests can use direct construction" + - "In mock classes (Mock* classes in utest files) - mocks are designed for testing" + - "For simple value initialization without external dependencies" + - "When the class is already easily testable without this pattern" + + benefits: + - "Enables dependency injection for unit testing" + - "Allows mocking of global dependencies" + - "Separates object construction from initialization side effects" + - "Makes code more testable without changing production behavior" + + rationale: "This pattern enables unit testing by allowing mock dependencies to be injected before initialization, while maintaining clean production code that calls assemble() immediately after construction. Unit test code and mock classes don't need this pattern because they are already designed for testing purposes." + codec_handling: enhanced_rtmp: description: | diff --git a/trunk/configure b/trunk/configure index 70d281a69..c515a2deb 100755 --- a/trunk/configure +++ b/trunk/configure @@ -382,8 +382,8 @@ if [[ $SRS_UTEST == YES ]]; then "srs_utest_st" "srs_utest_rtc2" "srs_utest_rtc3" "srs_utest_fmp4" "srs_utest_source_lock" "srs_utest_stream_token" "srs_utest_rtc_recv_track" "srs_utest_st2" "srs_utest_hevc_structs" "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_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") # Always include SRT utest MODULE_FILES+=("srs_utest_srt") if [[ $SRS_GB28181 == YES ]]; then diff --git a/trunk/src/app/srs_app_caster_flv.cpp b/trunk/src/app/srs_app_caster_flv.cpp index 55404dcc1..8ab5458f7 100644 --- a/trunk/src/app/srs_app_caster_flv.cpp +++ b/trunk/src/app/srs_app_caster_flv.cpp @@ -172,10 +172,11 @@ srs_error_t SrsAppCasterFlv::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa SrsDynamicHttpConn *dconn = dynamic_cast(hconn->handler()); srs_assert(dconn); - std::string app = srs_path_filepath_dir(r->path()); + SrsPath path; + std::string app = path.filepath_dir(r->path()); app = srs_strings_trim_start(app, "/"); - std::string stream = srs_path_filepath_base(r->path()); + std::string stream = path.filepath_base(r->path()); stream = srs_strings_trim_start(stream, "/"); std::string o = output_; diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 9c1e71293..86b8500b0 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -1576,7 +1576,8 @@ srs_error_t SrsConfig::parse_options(int argc, char **argv) } // Make sure config file exists. - if (!env_only_ && !srs_path_exists(config_file_)) { + SrsPath path; + if (!env_only_ && !path.exists(config_file_)) { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "no config file at %s", config_file_.c_str()); } @@ -1677,13 +1678,15 @@ srs_error_t SrsConfig::persistence() // do persistence to writer. if ((err = do_persistence(&fw)) != srs_success) { - ::unlink(path.c_str()); + SrsPath p; + p.unlink(path); return srs_error_wrap(err, "persistence"); } // rename the config file. if (::rename(path.c_str(), config_file_.c_str()) < 0) { - ::unlink(path.c_str()); + SrsPath p; + p.unlink(path); return srs_error_new(ERROR_SYSTEM_CONFIG_PERSISTENCE, "rename %s=>%s", path.c_str(), config_file_.c_str()); } @@ -2032,7 +2035,8 @@ srs_error_t SrsConfig::check_normal_config() return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "invalid stats.network=%d", get_stats_network()); } if (true) { - vector ips = srs_get_local_ips(); + SrsProtocolUtility utility; + vector ips = utility.local_ips(); int index = get_stats_network(); if (index >= (int)ips.size()) { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "invalid stats.network=%d of %d", diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 138670c2f..c02083a33 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -281,6 +281,88 @@ public: ISrsAppConfig(); virtual ~ISrsAppConfig(); +public: + virtual void subscribe(ISrsReloadHandler *handler) = 0; + virtual void unsubscribe(ISrsReloadHandler *handler) = 0; + virtual srs_error_t reload(SrsReloadState *pstate) = 0; + virtual srs_error_t persistence() = 0; + virtual std::string config() = 0; + +public: + // Global server config + virtual int get_max_connections() = 0; + virtual std::string get_pid_file() = 0; + virtual bool empty_ip_ok() = 0; + virtual bool get_asprocess() = 0; + virtual srs_utime_t get_grace_start_wait() = 0; + virtual srs_utime_t get_grace_final_wait() = 0; + virtual bool is_force_grace_quit() = 0; + virtual bool inotify_auto_reload() = 0; + virtual bool auto_reload_for_docker() = 0; + +public: + // RTMP config + virtual std::vector get_listens() = 0; + virtual bool get_rtmps_enabled() = 0; + virtual std::vector get_rtmps_listen() = 0; + +public: + // HTTP API config + virtual bool get_http_api_enabled() = 0; + virtual std::vector get_http_api_listens() = 0; + virtual bool get_https_api_enabled() = 0; + virtual std::vector get_https_api_listens() = 0; + virtual std::string get_https_api_ssl_key() = 0; + virtual std::string get_https_api_ssl_cert() = 0; + +public: + // HTTP Server config + virtual bool get_http_stream_enabled() = 0; + virtual std::vector get_http_stream_listens() = 0; + virtual bool get_https_stream_enabled() = 0; + virtual std::vector get_https_stream_listens() = 0; + virtual std::string get_https_stream_ssl_key() = 0; + virtual std::string get_https_stream_ssl_cert() = 0; + virtual std::string get_http_stream_dir() = 0; + +public: + // WebRTC config + virtual bool get_rtc_server_enabled() = 0; + virtual bool get_rtc_server_tcp_enabled() = 0; + virtual std::vector get_rtc_server_tcp_listens() = 0; + virtual std::string get_rtc_server_protocol() = 0; + virtual std::vector get_rtc_server_listens() = 0; + virtual int get_rtc_server_reuseport() = 0; + +public: + // RTSP config + virtual bool get_rtsp_server_enabled() = 0; + virtual std::vector get_rtsp_server_listens() = 0; + +public: + // SRT config + virtual std::vector get_srt_listens() = 0; + +public: + // Stream caster config + virtual std::vector get_stream_casters() = 0; + virtual bool get_stream_caster_enabled(SrsConfDirective *conf) = 0; + virtual std::string get_stream_caster_engine(SrsConfDirective *conf) = 0; + +public: + // Exporter config + virtual bool get_exporter_enabled() = 0; + virtual std::string get_exporter_listen() = 0; + +public: + // Stats config + virtual bool get_stats_enabled() = 0; + +public: + // Heartbeat config + virtual bool get_heartbeat_enabled() = 0; + virtual srs_utime_t get_heartbeat_interval() = 0; + public: virtual bool get_vhost_http_hooks_enabled(std::string vhost) = 0; virtual SrsConfDirective *get_vhost_on_stop(std::string vhost) = 0; @@ -326,6 +408,18 @@ public: virtual bool get_hls_ctx_enabled(std::string vhost) = 0; virtual bool get_hls_ts_ctx_enabled(std::string vhost) = 0; virtual bool get_hls_recover(std::string vhost) = 0; + virtual bool get_forward_enabled(std::string vhost) = 0; + virtual SrsConfDirective *get_forwards(std::string vhost) = 0; + virtual srs_utime_t get_queue_length(std::string vhost) = 0; + virtual SrsConfDirective *get_forward_backend(std::string vhost) = 0; + virtual bool get_atc(std::string vhost) = 0; + virtual int get_time_jitter(std::string vhost) = 0; + virtual bool get_mix_correct(std::string vhost) = 0; + virtual bool try_annexb_first(std::string vhost) = 0; + virtual bool get_vhost_is_edge(std::string vhost) = 0; + virtual bool get_atc_auto(std::string vhost) = 0; + virtual bool get_reduce_sequence_header(std::string vhost) = 0; + virtual bool get_parse_sps(std::string vhost) = 0; }; // The config service provider. diff --git a/trunk/src/app/srs_app_coworkers.cpp b/trunk/src/app/srs_app_coworkers.cpp index ed584bc0a..3d82a4542 100644 --- a/trunk/src/app/srs_app_coworkers.cpp +++ b/trunk/src/app/srs_app_coworkers.cpp @@ -80,7 +80,8 @@ SrsJsonAny *SrsCoWorkers::dumps(string vhost, string coworker, string app, strin service_ip = coworker_host; } if (service_ip.empty()) { - service_ip = srs_get_public_internet_address(); + SrsProtocolUtility utility; + service_ip = utility.public_internet_address(); } // The backend API endpoint. diff --git a/trunk/src/app/srs_app_dash.cpp b/trunk/src/app/srs_app_dash.cpp index c304c0b51..dc2b91c95 100644 --- a/trunk/src/app/srs_app_dash.cpp +++ b/trunk/src/app/srs_app_dash.cpp @@ -179,8 +179,11 @@ void SrsMpdWriter::dispose() if (req_) { string mpd_path = srs_path_build_stream(mpd_file_, req_->vhost_, req_->app_, req_->stream_); string full_path = home_ + "/" + mpd_path; - if (unlink(full_path.c_str()) < 0) { - srs_warn("ignore remove mpd failed, %s", full_path.c_str()); + SrsPath path; + srs_error_t err = path.unlink(full_path); + if (err != srs_success) { + srs_warn("ignore remove mpd failed, %s, %s", full_path.c_str(), srs_error_desc(err).c_str()); + srs_freep(err); } } } @@ -207,8 +210,9 @@ srs_error_t SrsMpdWriter::on_publish() home_ = _srs_config->get_dash_path(r->vhost_); mpd_file_ = _srs_config->get_dash_mpd_file(r->vhost_); + SrsPath path; string mpd_path = srs_path_build_stream(mpd_file_, req_->vhost_, req_->app_, req_->stream_); - fragment_home_ = srs_path_filepath_dir(mpd_path) + "/" + req_->stream_; + fragment_home_ = path.filepath_dir(mpd_path) + "/" + req_->stream_; window_size_ = _srs_config->get_dash_window_size(r->vhost_); srs_trace("DASH: Config fragment=%dms, period=%dms, window=%d, timeshit=%dms, home=%s, mpd=%s", @@ -230,13 +234,14 @@ srs_error_t SrsMpdWriter::write(SrsFormat *format, SrsFragmentWindow *afragments return err; } + SrsPath path; string mpd_path = srs_path_build_stream(mpd_file_, req_->vhost_, req_->app_, req_->stream_); string full_path = home_ + "/" + mpd_path; - string full_home = srs_path_filepath_dir(full_path); + string full_home = path.filepath_dir(full_path); - fragment_home_ = srs_path_filepath_dir(mpd_path) + "/" + req_->stream_; + fragment_home_ = path.filepath_dir(mpd_path) + "/" + req_->stream_; - if ((err = srs_os_mkdir_all(full_home)) != srs_success) { + if ((err = path.mkdir_all(full_home)) != srs_success) { return srs_error_wrap(err, "Create MPD home failed, home=%s", full_home.c_str()); } @@ -650,8 +655,9 @@ srs_error_t SrsDashController::refresh_init_mp4(SrsMediaPacket *msg, SrsFormat * return err; } + SrsPath path_util; string full_home = home_ + "/" + req_->app_ + "/" + req_->stream_; - if ((err = srs_os_mkdir_all(full_home)) != srs_success) { + if ((err = path_util.mkdir_all(full_home)) != srs_success) { return srs_error_wrap(err, "Create media home failed, home=%s", full_home.c_str()); } @@ -680,6 +686,14 @@ srs_error_t SrsDashController::refresh_init_mp4(SrsMediaPacket *msg, SrsFormat * return err; } +ISrsDash::ISrsDash() +{ +} + +ISrsDash::~ISrsDash() +{ +} + SrsDash::SrsDash() { hub_ = NULL; diff --git a/trunk/src/app/srs_app_dash.hpp b/trunk/src/app/srs_app_dash.hpp index 44d447658..edd45c2df 100644 --- a/trunk/src/app/srs_app_dash.hpp +++ b/trunk/src/app/srs_app_dash.hpp @@ -162,8 +162,28 @@ private: virtual srs_error_t refresh_init_mp4(SrsMediaPacket *msg, SrsFormat *format); }; +// The DASH interface. +class ISrsDash +{ +public: + ISrsDash(); + virtual ~ISrsDash(); + +public: + virtual void dispose() = 0; + virtual srs_error_t cycle() = 0; + virtual srs_utime_t cleanup_delay() = 0; + +public: + virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r) = 0; + virtual srs_error_t on_publish() = 0; + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format) = 0; + virtual srs_error_t on_video(SrsMediaPacket *shared_video, SrsFormat *format) = 0; + virtual void on_unpublish() = 0; +}; + // The MPEG-DASH encoder, transmux RTMP to DASH. -class SrsDash +class SrsDash : public ISrsDash { private: bool enabled_; diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 06800e570..ebea548a1 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -83,7 +83,8 @@ srs_error_t SrsDvrSegmenter::open() } string path = generate_path(); - if (srs_path_exists(path)) { + SrsPath path_util; + if (path_util.exists(path)) { return srs_error_new(ERROR_DVR_CANNOT_APPEND, "DVR can't append to exists path=%s", path.c_str()); } fragment_->set_path(path); @@ -903,6 +904,14 @@ srs_error_t SrsDvrSegmentPlan::update_duration(SrsMediaPacket *msg) return err; } +ISrsDvr::ISrsDvr() +{ +} + +ISrsDvr::~ISrsDvr() +{ +} + SrsDvr::SrsDvr() { hub_ = NULL; diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index d95915afb..d541663b6 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -233,8 +233,24 @@ private: virtual srs_error_t update_duration(SrsMediaPacket *msg); }; +// The DVR interface. +class ISrsDvr +{ +public: + ISrsDvr(); + virtual ~ISrsDvr(); + +public: + virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r) = 0; + virtual srs_error_t on_publish(ISrsRequest *r) = 0; + virtual void on_unpublish() = 0; + virtual srs_error_t on_meta_data(SrsMediaPacket *metadata) = 0; + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format) = 0; + virtual srs_error_t on_video(SrsMediaPacket *shared_video, SrsFormat *format) = 0; +}; + // DVR(Digital Video Recorder) to record RTMP stream to flv/mp4 file. -class SrsDvr : public ISrsReloadHandler +class SrsDvr : public ISrsReloadHandler, public ISrsDvr { private: SrsOriginHub *hub_; diff --git a/trunk/src/app/srs_app_encoder.cpp b/trunk/src/app/srs_app_encoder.cpp index f954d3151..826f7f835 100644 --- a/trunk/src/app/srs_app_encoder.cpp +++ b/trunk/src/app/srs_app_encoder.cpp @@ -21,6 +21,14 @@ using namespace std; // for encoder to detect the dead loop static std::vector _transcoded_url; +ISrsMediaEncoder::ISrsMediaEncoder() +{ +} + +ISrsMediaEncoder::~ISrsMediaEncoder() +{ +} + SrsEncoder::SrsEncoder() { trd_ = new SrsDummyCoroutine(); diff --git a/trunk/src/app/srs_app_encoder.hpp b/trunk/src/app/srs_app_encoder.hpp index 0c9b24c12..1b796a039 100644 --- a/trunk/src/app/srs_app_encoder.hpp +++ b/trunk/src/app/srs_app_encoder.hpp @@ -19,9 +19,24 @@ class ISrsRequest; class SrsPithyPrint; class SrsFFMPEG; +// The encoder interface. +class ISrsMediaEncoder +{ +public: + ISrsMediaEncoder(); + virtual ~ISrsMediaEncoder(); + +public: + virtual srs_error_t on_publish(ISrsRequest *req) = 0; + virtual void on_unpublish() = 0; + // Interface ISrsReusableThreadHandler. +public: + virtual srs_error_t cycle() = 0; +}; + // The encoder for a stream, may use multiple // ffmpegs to transcode the specified stream. -class SrsEncoder : public ISrsCoroutineHandler +class SrsEncoder : public ISrsCoroutineHandler, public ISrsMediaEncoder { private: std::string input_stream_name_; diff --git a/trunk/src/app/srs_app_factory.cpp b/trunk/src/app/srs_app_factory.cpp index 7e2394821..499b8b6ab 100644 --- a/trunk/src/app/srs_app_factory.cpp +++ b/trunk/src/app/srs_app_factory.cpp @@ -7,11 +7,12 @@ #include #include +#include #include #include -#include #include #include +#include SrsAppFactory::SrsAppFactory() { @@ -41,6 +42,18 @@ SrsPath *SrsAppFactory::create_path() return new SrsPath(); } +SrsLiveSource *SrsAppFactory::create_live_source() +{ + return new SrsLiveSource(); +} + +ISrsOriginHub *SrsAppFactory::create_origin_hub() +{ + SrsOriginHub *hub = new SrsOriginHub(); + hub->assemble(); + return hub; +} + SrsFinalFactory::SrsFinalFactory() { } diff --git a/trunk/src/app/srs_app_factory.hpp b/trunk/src/app/srs_app_factory.hpp index b54c0b774..8d29e46e8 100644 --- a/trunk/src/app/srs_app_factory.hpp +++ b/trunk/src/app/srs_app_factory.hpp @@ -14,6 +14,8 @@ class ISrsFileWriter; class ISrsFileReader; class SrsPath; +class SrsLiveSource; +class ISrsOriginHub; // The factory to create app objects. class SrsAppFactory @@ -27,6 +29,8 @@ public: virtual ISrsFileWriter *create_enc_file_writer(); virtual ISrsFileReader *create_file_reader(); virtual SrsPath *create_path(); + virtual SrsLiveSource *create_live_source(); + virtual ISrsOriginHub *create_origin_hub(); }; extern SrsAppFactory *_srs_app_factory; diff --git a/trunk/src/app/srs_app_forward.cpp b/trunk/src/app/srs_app_forward.cpp index 69a5a9521..f7b5c3862 100644 --- a/trunk/src/app/srs_app_forward.cpp +++ b/trunk/src/app/srs_app_forward.cpp @@ -30,6 +30,14 @@ using namespace std; #include #include +ISrsForwarder::ISrsForwarder() +{ +} + +ISrsForwarder::~ISrsForwarder() +{ +} + SrsForwarder::SrsForwarder(SrsOriginHub *h) { hub_ = h; diff --git a/trunk/src/app/srs_app_forward.hpp b/trunk/src/app/srs_app_forward.hpp index 3a22208e1..6891b0458 100644 --- a/trunk/src/app/srs_app_forward.hpp +++ b/trunk/src/app/srs_app_forward.hpp @@ -25,8 +25,25 @@ class SrsOriginHub; class SrsKbps; class SrsSimpleRtmpClient; +// The forward interface. +class ISrsForwarder +{ +public: + ISrsForwarder(); + virtual ~ISrsForwarder(); + +public: + virtual srs_error_t initialize(ISrsRequest *r, std::string ep) = 0; + virtual void set_queue_size(srs_utime_t queue_size) = 0; + virtual srs_error_t on_publish() = 0; + virtual void on_unpublish() = 0; + virtual srs_error_t on_meta_data(SrsMediaPacket *shared_metadata) = 0; + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio) = 0; + virtual srs_error_t on_video(SrsMediaPacket *shared_video) = 0; +}; + // Forward the stream to other servers. -class SrsForwarder : public ISrsCoroutineHandler +class SrsForwarder : public ISrsCoroutineHandler, public ISrsForwarder { private: // The ep to forward, server[:port]. diff --git a/trunk/src/app/srs_app_fragment.cpp b/trunk/src/app/srs_app_fragment.cpp index cc9a4017d..05a30b201 100644 --- a/trunk/src/app/srs_app_fragment.cpp +++ b/trunk/src/app/srs_app_fragment.cpp @@ -81,8 +81,9 @@ srs_error_t SrsFragment::unlink_file() { srs_error_t err = srs_success; - if (::unlink(filepath_.c_str()) < 0) { - return srs_error_new(ERROR_SYSTEM_FRAGMENT_UNLINK, "unlink %s", filepath_.c_str()); + SrsPath path; + if ((err = path.unlink(filepath_)) != srs_success) { + return srs_error_wrap(err, "unlink %s", filepath_.c_str()); } return err; @@ -92,9 +93,10 @@ srs_error_t SrsFragment::create_dir() { srs_error_t err = srs_success; - std::string segment_dir = srs_path_filepath_dir(filepath_); + SrsPath path; + std::string segment_dir = path.filepath_dir(filepath_); - if ((err = srs_os_mkdir_all(segment_dir)) != srs_success) { + if ((err = path.mkdir_all(segment_dir)) != srs_success) { return srs_error_wrap(err, "create %s", segment_dir.c_str()); } @@ -113,8 +115,9 @@ srs_error_t SrsFragment::unlink_tmpfile() srs_error_t err = srs_success; string filepath = tmppath(); - if (::unlink(filepath.c_str()) < 0) { - return srs_error_new(ERROR_SYSTEM_FRAGMENT_UNLINK, "unlink tmp file %s", filepath.c_str()); + SrsPath path; + if ((err = path.unlink(filepath)) != srs_success) { + return srs_error_wrap(err, "unlink tmp file %s", filepath.c_str()); } return err; diff --git a/trunk/src/app/srs_app_hds.cpp b/trunk/src/app/srs_app_hds.cpp index dffdbcb8e..974d8aeeb 100644 --- a/trunk/src/app/srs_app_hds.cpp +++ b/trunk/src/app/srs_app_hds.cpp @@ -232,6 +232,14 @@ private: string path_; }; +ISrsHds::ISrsHds() +{ +} + +ISrsHds::~ISrsHds() +{ +} + SrsHds::SrsHds() : currentSegment_(NULL), fragment_index_(1), video_sh_(NULL), audio_sh_(NULL), hds_req_(NULL), hds_enabled_(false) { @@ -407,8 +415,9 @@ srs_error_t SrsHds::flush_mainfest() "", hds_req_->stream_.c_str(), hds_req_->stream_.c_str(), hds_req_->stream_.c_str()); + SrsPath path_util; string dir = _srs_config->get_hds_path(hds_req_->vhost_) + "/" + hds_req_->app_; - if ((err = srs_os_mkdir_all(dir)) != srs_success) { + if ((err = path_util.mkdir_all(dir)) != srs_success) { return srs_error_wrap(err, "hds create dir failed"); } string path = dir + "/" + hds_req_->stream_ + ".f4m"; @@ -689,7 +698,8 @@ void SrsHds::adjust_windows() double windows_size_limit = srsu2ms(_srs_config->get_hds_window(hds_req_->vhost_)); if (windows_size > windows_size_limit) { SrsHdsFragment *fragment = fragments_.front(); - unlink(fragment->fragment_path().c_str()); + SrsPath path; + path.unlink(fragment->fragment_path()); fragments_.erase(fragments_.begin()); srs_freep(fragment); } diff --git a/trunk/src/app/srs_app_hds.hpp b/trunk/src/app/srs_app_hds.hpp index 6be937dab..59562c051 100644 --- a/trunk/src/app/srs_app_hds.hpp +++ b/trunk/src/app/srs_app_hds.hpp @@ -18,8 +18,22 @@ class SrsMediaPacket; class SrsHdsFragment; class SrsLiveSource; +// The HDS interface. +class ISrsHds +{ +public: + ISrsHds(); + virtual ~ISrsHds(); + +public: + virtual srs_error_t on_publish(ISrsRequest *req) = 0; + virtual srs_error_t on_unpublish() = 0; + virtual srs_error_t on_video(SrsMediaPacket *msg) = 0; + virtual srs_error_t on_audio(SrsMediaPacket *msg) = 0; +}; + // Mux RTMP to Adobe HDS streaming. -class SrsHds +class SrsHds : public ISrsHds { public: SrsHds(); diff --git a/trunk/src/app/srs_app_heartbeat.cpp b/trunk/src/app/srs_app_heartbeat.cpp index 72d91354f..e09d4d67d 100644 --- a/trunk/src/app/srs_app_heartbeat.cpp +++ b/trunk/src/app/srs_app_heartbeat.cpp @@ -57,7 +57,8 @@ srs_error_t SrsHttpHeartbeat::do_heartbeat() ip = srs_getenv("srs.device.ip"); // SRS_DEVICE_IP if (ip.empty()) { // Use the local ip address specified by the stats.network config. - vector &ips = srs_get_local_ips(); + SrsProtocolUtility utility; + vector &ips = utility.local_ips(); if (!ips.empty()) { ip = ips[_srs_config->get_stats_network() % (int)ips.size()]->ip_; } diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index 30a0d7d64..3a21c294f 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -527,17 +527,18 @@ srs_error_t SrsHlsFmp4Muxer::write_init_mp4(SrsFormat *format, bool has_video, b init_file = srs_path_build_stream(init_file, vhost, app, stream); std::string hls_path = config_->get_hls_path(vhost); - std::string path = hls_path + "/" + init_file; + std::string filepath = hls_path + "/" + init_file; // Create directory for the init file - std::string init_dir = srs_path_filepath_dir(path); - if ((err = srs_os_mkdir_all(init_dir)) != srs_success) { + SrsPath path; + std::string init_dir = path.filepath_dir(filepath); + if ((err = path.mkdir_all(init_dir)) != srs_success) { return srs_error_wrap(err, "Create init mp4 dir failed, dir=%s", init_dir.c_str()); } SrsUniquePtr init_mp4(new SrsInitMp4Segment(writer_)); - init_mp4->set_path(path); + init_mp4->set_path(filepath); if (hls_keys_) { init_mp4->config_cipher(kid_, iv_, 16); @@ -578,7 +579,7 @@ srs_error_t SrsHlsFmp4Muxer::write_init_mp4(SrsFormat *format, bool has_video, b init_mp4_uri += "/"; // add the http dir to uri. - string http_dir = srs_path_filepath_dir(m3u8_url_); + string http_dir = path.filepath_dir(m3u8_url_); if (!http_dir.empty()) { init_mp4_uri += http_dir + "/"; } @@ -587,7 +588,7 @@ srs_error_t SrsHlsFmp4Muxer::write_init_mp4(SrsFormat *format, bool has_video, b // Convert to relative URI for m3u8 playlist. // TODO: Need to resolve the relative URI from m3u8 and init file. - init_mp4_uri_ = srs_path_filepath_base(init_file); + init_mp4_uri_ = path.filepath_base(init_file); // use async to call the http hooks, for it will cause thread switch. if ((err = async_->execute(new SrsDvrAsyncCallOnHls(_srs_context->get_id(), req_, init_mp4->fullpath(), @@ -708,16 +709,17 @@ srs_error_t SrsHlsFmp4Muxer::update_config(ISrsRequest *r) max_td_ = hls_fragment_ * hls_td_ratio; // create m3u8 dir once. - m3u8_dir_ = srs_path_filepath_dir(m3u8_); - if ((err = srs_os_mkdir_all(m3u8_dir_)) != srs_success) { + SrsPath path; + m3u8_dir_ = path.filepath_dir(m3u8_); + if ((err = path.mkdir_all(m3u8_dir_)) != srs_success) { return srs_error_wrap(err, "create dir"); } if (hls_keys_ && (hls_path_ != hls_key_file_path_)) { string key_file = srs_path_build_stream(hls_key_file_, vhost, app, stream); string key_url = hls_key_file_path_ + "/" + key_file; - string key_dir = srs_path_filepath_dir(key_url); - if ((err = srs_os_mkdir_all(key_dir)) != srs_success) { + string key_dir = path.filepath_dir(key_url); + if ((err = path.mkdir_all(key_dir)) != srs_success) { return srs_error_wrap(err, "create dir"); } } @@ -806,7 +808,8 @@ srs_error_t SrsHlsFmp4Muxer::segment_open(srs_utime_t basetime) current_->uri_ += "/"; // add the http dir to uri. - string http_dir = srs_path_filepath_dir(m3u8_url_); + SrsPath path; + string http_dir = path.filepath_dir(m3u8_url_); if (!http_dir.empty()) { current_->uri_ += http_dir + "/"; } @@ -1062,6 +1065,7 @@ srs_error_t SrsHlsFmp4Muxer::do_refresh_m3u8(std::string m3u8_file) // {file name}\n // TODO get segment name in relative path. + SrsPath path; std::string seg_uri = segment->fullpath(); if (true) { std::stringstream stemp; @@ -1069,7 +1073,7 @@ srs_error_t SrsHlsFmp4Muxer::do_refresh_m3u8(std::string m3u8_file) seg_uri = srs_strings_replace(seg_uri, "[duration]", stemp.str()); } // ss << segment->uri << SRS_CONSTS_LF; - ss << srs_path_filepath_base(seg_uri) << SRS_CONSTS_LF; + ss << path.filepath_base(seg_uri) << SRS_CONSTS_LF; } // write m3u8 to writer. @@ -1279,16 +1283,17 @@ srs_error_t SrsHlsMuxer::update_config(ISrsRequest *r, string entry_prefix, max_td_ = fragment * config_->get_hls_td_ratio(r->vhost_); // create m3u8 dir once. - m3u8_dir_ = srs_path_filepath_dir(m3u8_); - if ((err = srs_os_mkdir_all(m3u8_dir_)) != srs_success) { + SrsPath path_util; + m3u8_dir_ = path_util.filepath_dir(m3u8_); + if ((err = path_util.mkdir_all(m3u8_dir_)) != srs_success) { return srs_error_wrap(err, "create dir"); } if (hls_keys_ && (hls_path_ != hls_key_file_path_)) { string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_); string key_url = hls_key_file_path_ + "/" + key_file; - string key_dir = srs_path_filepath_dir(key_url); - if ((err = srs_os_mkdir_all(key_dir)) != srs_success) { + string key_dir = path_util.filepath_dir(key_url); + if ((err = path_util.mkdir_all(key_dir)) != srs_success) { return srs_error_wrap(err, "create dir"); } } @@ -1549,7 +1554,8 @@ srs_error_t SrsHlsMuxer::segment_open() current_->uri_ += "/"; // add the http dir to uri. - string http_dir = srs_path_filepath_dir(m3u8_url_); + SrsPath path; + string http_dir = path.filepath_dir(m3u8_url_); if (!http_dir.empty()) { current_->uri_ += http_dir + "/"; } @@ -2455,6 +2461,14 @@ int SrsHlsMp4Controller::deviation() return muxer_->deviation(); } +ISrsHls::ISrsHls() +{ +} + +ISrsHls::~ISrsHls() +{ +} + SrsHls::SrsHls() { req_ = NULL; diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index 2d35b2503..9826119d0 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -317,6 +317,7 @@ private: public: // HLS recover mode. srs_error_t recover_hls(); + private: virtual srs_error_t do_recover_hls(); }; @@ -612,9 +613,29 @@ public: virtual int deviation(); }; +// The HLS interface. +class ISrsHls +{ +public: + ISrsHls(); + virtual ~ISrsHls(); + +public: + virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r) = 0; + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format) = 0; + virtual srs_error_t on_video(SrsMediaPacket *shared_video, SrsFormat *format) = 0; + virtual srs_error_t on_publish() = 0; + virtual void on_unpublish() = 0; + +public: + virtual void dispose() = 0; + virtual srs_error_t cycle() = 0; + virtual srs_utime_t cleanup_delay() = 0; +}; + // Transmux RTMP stream to HLS(m3u8 and ts,fmp4). // TODO: FIXME: add utest for hls. -class SrsHls +class SrsHls : public ISrsHls { private: ISrsAppConfig *config_; diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index 147eb07b6..e72927eb0 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -1278,7 +1278,8 @@ srs_error_t SrsGoApiMetrics::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa #if defined(__linux__) || defined(SRS_OSX) // Get system info - utsname *system_info = srs_get_system_uname_info(); + SrsProtocolUtility utility; + utsname *system_info = utility.system_uname(); ss << "# HELP srs_node_uname_info Labeled system information as provided by the uname system call.\n" << "# TYPE srs_node_uname_info gauge\n" << "srs_node_uname_info{" diff --git a/trunk/src/app/srs_app_http_hooks.cpp b/trunk/src/app/srs_app_http_hooks.cpp index ec6ea2802..5ea3ea2f1 100644 --- a/trunk/src/app/srs_app_http_hooks.cpp +++ b/trunk/src/app/srs_app_http_hooks.cpp @@ -349,7 +349,8 @@ srs_error_t SrsHttpHooks::on_hls(SrsContextId c, string url, ISrsRequest *req, s std::string cwd = _srs_config->cwd(); // the ts_url is under the same dir of m3u8_url. - string prefix = srs_path_filepath_dir(m3u8_url); + SrsPath path; + string prefix = path.filepath_dir(m3u8_url); if (!prefix.empty() && !srs_net_url_is_http(ts_url)) { ts_url = prefix + "/" + ts_url; } diff --git a/trunk/src/app/srs_app_http_static.cpp b/trunk/src/app/srs_app_http_static.cpp index dd8ce1082..aef787f10 100644 --- a/trunk/src/app/srs_app_http_static.cpp +++ b/trunk/src/app/srs_app_http_static.cpp @@ -92,7 +92,8 @@ srs_error_t SrsHlsStream::serve_m3u8_ctx(ISrsHttpResponseWriter *w, ISrsHttpMess // Correct the app and stream by path, which is created from template. // @remark Be careful that the stream has extension now, might cause identify fail. - req->stream_ = srs_path_filepath_base(r->path()); + SrsPath path; + req->stream_ = path.filepath_base(r->path()); // Served by us. *served = true; @@ -152,9 +153,10 @@ srs_error_t SrsHlsStream::serve_new_session(ISrsHttpResponseWriter *w, ISrsHttpM srs_assert(hr); if (ctx.empty()) { + SrsRand rand; // make sure unique do { - ctx = srs_rand_gen_str(8); // the same as cid + ctx = rand.gen_str(8); // the same as cid } while (ctx_is_exist(ctx)); } diff --git a/trunk/src/app/srs_app_http_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp index 839a676a9..8005d47fe 100644 --- a/trunk/src/app/srs_app_http_stream.cpp +++ b/trunk/src/app/srs_app_http_stream.cpp @@ -655,9 +655,10 @@ srs_error_t SrsLiveStream::serve_http_impl(ISrsHttpResponseWriter *w, ISrsHttpMe // Correct the app and stream by path, which is created from template. // @remark Be careful that the stream has extension now, might cause identify fail. - req_->stream_ = srs_path_filepath_base(r->path()); + SrsPath path; + req_->stream_ = path.filepath_base(r->path()); // remove the extension of stream if have. for instance, test.flv -> test - req_->stream_ = srs_path_filepath_filename(req_->stream_); + req_->stream_ = path.filepath_filename(req_->stream_); // update client ip req_->ip_ = hc->remote_ip(); @@ -983,7 +984,8 @@ SrsLiveEntry::SrsLiveEntry(std::string m) req_ = NULL; - std::string ext = srs_path_filepath_ext(m); + SrsPath path; + std::string ext = path.filepath_ext(m); is_flv_ = (ext == ".flv"); is_ts_ = (ext == ".ts"); is_mp3_ = (ext == ".mp3"); diff --git a/trunk/src/app/srs_app_ingest.cpp b/trunk/src/app/srs_app_ingest.cpp index 9435246a3..9358f4476 100644 --- a/trunk/src/app/srs_app_ingest.cpp +++ b/trunk/src/app/srs_app_ingest.cpp @@ -474,7 +474,8 @@ void SrsIngester::show_ingest_log_message() } // random choose one ingester to report. - int index = srs_rand_integer() % (int)ingesters_.size(); + SrsRand rand; + int index = rand.integer() % (int)ingesters_.size(); SrsIngesterFFMPEG *ingester = ingesters_.at(index); // reportable diff --git a/trunk/src/app/srs_app_latest_version.cpp b/trunk/src/app/srs_app_latest_version.cpp index 1537875e2..ed45adb42 100644 --- a/trunk/src/app/srs_app_latest_version.cpp +++ b/trunk/src/app/srs_app_latest_version.cpp @@ -203,7 +203,8 @@ srs_error_t SrsLatestVersion::cycle() if (true) { srs_utime_t first_wait_for_qlv = _srs_config->first_wait_for_qlv(); - string pip = srs_get_public_internet_address(); + SrsProtocolUtility utility; + string pip = utility.public_internet_address(); srs_trace("Startup query id=%s, session=%s, eip=%s, wait=%ds", server_id_.c_str(), session_id_.c_str(), pip.c_str(), srsu2msi(first_wait_for_qlv) / 1000); srs_usleep(first_wait_for_qlv); } @@ -220,8 +221,9 @@ srs_error_t SrsLatestVersion::cycle() srs_freep(err); // Ignore any error. } + SrsProtocolUtility utility2; srs_trace("Finish query id=%s, session=%s, eip=%s, match=%s, stable=%s, cost=%dms, url=%s", - server_id_.c_str(), session_id_.c_str(), srs_get_public_internet_address().c_str(), match_version_.c_str(), + server_id_.c_str(), session_id_.c_str(), utility2.public_internet_address().c_str(), match_version_.c_str(), stable_version_.c_str(), srsu2msi(srs_time_now_realtime() - starttime), url.c_str()); srs_usleep(3600 * SRS_UTIME_SECONDS); // Every an hour. @@ -235,11 +237,12 @@ srs_error_t SrsLatestVersion::query_latest_version(string &url) srs_error_t err = srs_success; // Generate uri and parse to object. + SrsProtocolUtility utility3; stringstream ss; ss << "http://api.ossrs.net/service/v1/releases?" << "version=v" << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_REVISION << "&id=" << server_id_ << "&session=" << session_id_ << "&role=srs" - << "&eip=" << srs_get_public_internet_address() + << "&eip=" << utility3.public_internet_address() << "&ts=" << srs_time_now_cached() << "&alive=" << srsu2ms(srs_time_now_cached() - srs_time_since_startup()) / 1000; srs_build_features(ss); diff --git a/trunk/src/app/srs_app_ng_exec.cpp b/trunk/src/app/srs_app_ng_exec.cpp index 758e36864..e9a6dce0a 100644 --- a/trunk/src/app/srs_app_ng_exec.cpp +++ b/trunk/src/app/srs_app_ng_exec.cpp @@ -20,6 +20,14 @@ using namespace std; #include #include +ISrsNgExec::ISrsNgExec() +{ +} + +ISrsNgExec::~ISrsNgExec() +{ +} + SrsNgExec::SrsNgExec() { trd_ = new SrsDummyCoroutine(); diff --git a/trunk/src/app/srs_app_ng_exec.hpp b/trunk/src/app/srs_app_ng_exec.hpp index 55fd4db47..eaf3ead3e 100644 --- a/trunk/src/app/srs_app_ng_exec.hpp +++ b/trunk/src/app/srs_app_ng_exec.hpp @@ -18,10 +18,23 @@ class ISrsRequest; class SrsPithyPrint; class SrsProcess; +// The ng-exec interface. +class ISrsNgExec +{ +public: + ISrsNgExec(); + virtual ~ISrsNgExec(); + +public: + virtual srs_error_t on_publish(ISrsRequest *req) = 0; + virtual void on_unpublish() = 0; + virtual srs_error_t cycle() = 0; +}; + // The ng-exec is the exec feature introduced by nginx-rtmp, // @see https://github.com/arut/nginx-rtmp-module/wiki/Directives#exec_push // @see https://github.com/ossrs/srs/issues/367 -class SrsNgExec : public ISrsCoroutineHandler +class SrsNgExec : public ISrsCoroutineHandler, public ISrsNgExec { private: ISrsCoroutine *trd_; diff --git a/trunk/src/app/srs_app_process.cpp b/trunk/src/app/srs_app_process.cpp index 2bad65e91..61ab1868c 100644 --- a/trunk/src/app/srs_app_process.cpp +++ b/trunk/src/app/srs_app_process.cpp @@ -299,7 +299,8 @@ void SrsProcess::stop() // when rewind, upstream will stop publish(unpublish), // unpublish event will stop all ffmpeg encoders, // then publish will start all ffmpeg encoders. - srs_error_t err = srs_kill_forced(pid_); + SrsAppUtility utility; + srs_error_t err = utility.kill(pid_); if (err != srs_success) { srs_warn("ignore kill the process failed, pid=%d. err=%s", pid_, srs_error_desc(err).c_str()); srs_freep(err); diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index 34a15934b..7da3d0964 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -2140,7 +2140,8 @@ srs_error_t SrsRtcConnection::initialize(ISrsRequest *r, bool dtls, bool srtp, s srs_error_t err = srs_success; username_ = username; - token_ = srs_rand_gen_str(9); + SrsRand rand; + token_ = rand.gen_str(9); req_ = r->copy(); SrsSessionConfig *cfg = &local_sdp_.session_negotiate_; @@ -3699,7 +3700,8 @@ srs_error_t SrsRtcPlayerNegotiator::generate_play_local_sdp(ISrsRequest *req, Sr local_sdp.group_policy_ = "BUNDLE"; - std::string cname = srs_rand_gen_str(16); + SrsRand rand; + std::string cname = rand.gen_str(16); if (audio_before_video) { if ((err = generate_play_local_sdp_for_audio(local_sdp, stream_desc, cname)) != srs_success) { diff --git a/trunk/src/app/srs_app_rtc_dtls.cpp b/trunk/src/app/srs_app_rtc_dtls.cpp index 19e171f0e..2530e66f4 100644 --- a/trunk/src/app/srs_app_rtc_dtls.cpp +++ b/trunk/src/app/srs_app_rtc_dtls.cpp @@ -300,7 +300,8 @@ srs_error_t SrsDtlsCertificate::initialize() X509_NAME *subject = X509_NAME_new(); srs_assert(subject); - int serial = (int)srs_rand_integer(); + SrsRand rand; + int serial = (int)rand.integer(); ASN1_INTEGER_set(X509_get_serialNumber(dtls_cert_), serial); const std::string &aor = RTMP_SIG_SRS_DOMAIN; diff --git a/trunk/src/app/srs_app_rtc_server.cpp b/trunk/src/app/srs_app_rtc_server.cpp index b8555a4ac..527148091 100644 --- a/trunk/src/app/srs_app_rtc_server.cpp +++ b/trunk/src/app/srs_app_rtc_server.cpp @@ -242,7 +242,8 @@ set discover_candidates(SrsRtcUserConfig *ruc) } // All automatically detected IP list. - vector &ips = srs_get_local_ips(); + SrsProtocolUtility utility; + vector &ips = utility.local_ips(); if (ips.empty()) { return candidate_ips; } @@ -402,8 +403,9 @@ srs_error_t SrsRtcSessionManager::do_create_rtc_session(SrsRtcUserConfig *ruc, S // All tracks default as inactive, so we must enable them. session->set_all_tracks_status(req->get_stream_url(), ruc->publish_, true); - std::string local_pwd = ruc->req_->ice_pwd_.empty() ? srs_rand_gen_str(32) : ruc->req_->ice_pwd_; - std::string local_ufrag = ruc->req_->ice_ufrag_.empty() ? srs_rand_gen_str(8) : ruc->req_->ice_ufrag_; + SrsRand rand; + std::string local_pwd = ruc->req_->ice_pwd_.empty() ? rand.gen_str(32) : ruc->req_->ice_pwd_; + std::string local_ufrag = ruc->req_->ice_ufrag_.empty() ? rand.gen_str(8) : ruc->req_->ice_ufrag_; // TODO: FIXME: Rename for a better name, it's not an username. std::string username = ""; while (true) { @@ -413,7 +415,7 @@ srs_error_t SrsRtcSessionManager::do_create_rtc_session(SrsRtcUserConfig *ruc, S } // Username conflict, regenerate a new one. - local_ufrag = srs_rand_gen_str(8); + local_ufrag = rand.gen_str(8); } local_sdp.set_ice_ufrag(local_ufrag); diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp index 8c350fe6d..5ead4963f 100644 --- a/trunk/src/app/srs_app_rtc_source.cpp +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -489,13 +489,15 @@ void SrsRtcSource::init_for_play_before_publishing() SrsUniquePtr stream_desc(new SrsRtcSourceDescription()); + SrsRand rand; + // audio track description if (true) { SrsRtcTrackDescription *audio_track_desc = new SrsRtcTrackDescription(); stream_desc->audio_track_desc_ = audio_track_desc; audio_track_desc->type_ = "audio"; - audio_track_desc->id_ = "audio-" + srs_rand_gen_str(8); + audio_track_desc->id_ = "audio-" + rand.gen_str(8); uint32_t audio_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc(); audio_track_desc->ssrc_ = audio_ssrc; @@ -512,7 +514,7 @@ void SrsRtcSource::init_for_play_before_publishing() stream_desc->video_track_descs_.push_back(h264_track_desc); h264_track_desc->type_ = "video"; - h264_track_desc->id_ = "video-h264-" + srs_rand_gen_str(8); + h264_track_desc->id_ = "video-h264-" + rand.gen_str(8); uint32_t h264_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc(); h264_track_desc->ssrc_ = h264_ssrc; @@ -530,7 +532,7 @@ void SrsRtcSource::init_for_play_before_publishing() stream_desc->video_track_descs_.push_back(h265_track_desc); h265_track_desc->type_ = "video"; - h265_track_desc->id_ = "video-h265-" + srs_rand_gen_str(8); + h265_track_desc->id_ = "video-h265-" + rand.gen_str(8); uint32_t h265_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc(); h265_track_desc->ssrc_ = h265_ssrc; diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp index eee9692d7..209b421b1 100644 --- a/trunk/src/app/srs_app_rtc_source.hpp +++ b/trunk/src/app/srs_app_rtc_source.hpp @@ -170,7 +170,7 @@ public: }; // The RTC source manager. -class SrsRtcSourceManager : public ISrsRtcSourceManager, public ISrsHourGlass +class SrsRtcSourceManager : public ISrsRtcSourceManager, public ISrsHourGlassHandler { private: srs_mutex_t lock_; @@ -183,7 +183,7 @@ public: public: virtual srs_error_t initialize(); - // interface ISrsHourGlass + // interface ISrsHourGlassHandler private: virtual srs_error_t setup_ticks(); virtual srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick); diff --git a/trunk/src/app/srs_app_rtmp_conn.cpp b/trunk/src/app/srs_app_rtmp_conn.cpp index 76fb44104..0c1285160 100644 --- a/trunk/src/app/srs_app_rtmp_conn.cpp +++ b/trunk/src/app/srs_app_rtmp_conn.cpp @@ -70,7 +70,8 @@ SrsSimpleRtmpClient::~SrsSimpleRtmpClient() srs_error_t SrsSimpleRtmpClient::connect_app() { - std::vector &ips = srs_get_local_ips(); + SrsProtocolUtility utility; + std::vector &ips = utility.local_ips(); srs_assert(_srs_config->get_stats_network() < (int)ips.size()); SrsIPAddress *local_ip = ips[_srs_config->get_stats_network()]; diff --git a/trunk/src/app/srs_app_rtmp_source.cpp b/trunk/src/app/srs_app_rtmp_source.cpp index b8f8e142a..2260b2a97 100644 --- a/trunk/src/app/srs_app_rtmp_source.cpp +++ b/trunk/src/app/srs_app_rtmp_source.cpp @@ -15,6 +15,7 @@ using namespace std; #include #include #include +#include #include #include #include @@ -403,7 +404,7 @@ ISrsWakable::~ISrsWakable() { } -SrsLiveConsumer::SrsLiveConsumer(SrsLiveSource *s) +SrsLiveConsumer::SrsLiveConsumer(ISrsLiveSource *s) { source_ = s; paused_ = false; @@ -817,6 +818,14 @@ SrsMediaPacket *SrsMixQueue::pop() return msg; } +ISrsOriginHub::ISrsOriginHub() +{ +} + +ISrsOriginHub::~ISrsOriginHub() +{ +} + SrsOriginHub::SrsOriginHub() { source_ = NULL; @@ -832,17 +841,24 @@ SrsOriginHub::SrsOriginHub() #endif ng_exec_ = new SrsNgExec(); - _srs_config->subscribe(this); + config_ = _srs_config; + stat_ = _srs_stat; + hooks_ = _srs_hooks; +} + +void SrsOriginHub::assemble() +{ + config_->subscribe(this); } SrsOriginHub::~SrsOriginHub() { - _srs_config->unsubscribe(this); + config_->unsubscribe(this); if (true) { - std::vector::iterator it; + std::vector::iterator it; for (it = forwarders_.begin(); it != forwarders_.end(); ++it) { - SrsForwarder *forwarder = *it; + ISrsForwarder *forwarder = *it; srs_freep(forwarder); } forwarders_.clear(); @@ -856,6 +872,10 @@ SrsOriginHub::~SrsOriginHub() #ifdef SRS_HDS srs_freep(hds_); #endif + + config_ = NULL; + stat_ = NULL; + hooks_ = NULL; } // CRITICAL: This method is called AFTER the source has been added to the source pool @@ -926,9 +946,9 @@ srs_error_t SrsOriginHub::on_meta_data(SrsMediaPacket *shared_metadata, SrsOnMet // copy to all forwarders if (true) { - std::vector::iterator it; + std::vector::iterator it; for (it = forwarders_.begin(); it != forwarders_.end(); ++it) { - SrsForwarder *forwarder = *it; + ISrsForwarder *forwarder = *it; if ((err = forwarder->on_meta_data(shared_metadata)) != srs_success) { return srs_error_wrap(err, "Forwarder consume metadata"); } @@ -947,7 +967,7 @@ srs_error_t SrsOriginHub::on_audio(SrsMediaPacket *shared_audio) srs_error_t err = srs_success; SrsMediaPacket *msg = shared_audio; - SrsRtmpFormat *format = source_->format_; + SrsRtmpFormat *format = source_->format(); // Handle the metadata when got sequence header. if (format->is_aac_sequence_header() || format->is_mp3_sequence_header()) { @@ -958,8 +978,7 @@ srs_error_t SrsOriginHub::on_audio(SrsMediaPacket *shared_audio) static int flv_sound_types[] = {1, 2, 0}; // when got audio stream info. - SrsStatistic *stat = _srs_stat; - if ((err = stat->on_audio_info(req_, format->acodec_->id_, c->sound_rate_, c->sound_type_, c->aac_object_)) != srs_success) { + if ((err = stat_->on_audio_info(req_, format->acodec_->id_, c->sound_rate_, c->sound_type_, c->aac_object_)) != srs_success) { return srs_error_wrap(err, "stat audio"); } @@ -978,13 +997,13 @@ srs_error_t SrsOriginHub::on_audio(SrsMediaPacket *shared_audio) if ((err = hls_->on_audio(msg, format)) != srs_success) { // apply the error strategy for hls. - std::string hls_error_strategy = _srs_config->get_hls_on_error(req_->vhost_); + std::string hls_error_strategy = config_->get_hls_on_error(req_->vhost_); if (srs_config_hls_is_on_error_ignore(hls_error_strategy)) { srs_warn("hls: ignore audio error %s", srs_error_desc(err).c_str()); hls_->on_unpublish(); srs_freep(err); } else if (srs_config_hls_is_on_error_continue(hls_error_strategy)) { - if (srs_hls_can_continue(srs_error_code(err), source_->meta_->ash(), msg)) { + if (srs_hls_can_continue(srs_error_code(err), source_->meta()->ash(), msg)) { srs_freep(err); } else { return srs_error_wrap(err, "hls: audio"); @@ -1016,9 +1035,9 @@ srs_error_t SrsOriginHub::on_audio(SrsMediaPacket *shared_audio) // copy to all forwarders. if (true) { - std::vector::iterator it; + std::vector::iterator it; for (it = forwarders_.begin(); it != forwarders_.end(); ++it) { - SrsForwarder *forwarder = *it; + ISrsForwarder *forwarder = *it; if ((err = forwarder->on_audio(msg)) != srs_success) { return srs_error_wrap(err, "forward: audio"); } @@ -1033,7 +1052,7 @@ srs_error_t SrsOriginHub::on_video(SrsMediaPacket *shared_video, bool is_sequenc srs_error_t err = srs_success; SrsMediaPacket *msg = shared_video; - SrsRtmpFormat *format = source_->format_; + SrsRtmpFormat *format = source_->format(); // cache the sequence header if h264 // donot cache the sequence header to gop_cache, return here. @@ -1042,15 +1061,13 @@ srs_error_t SrsOriginHub::on_video(SrsMediaPacket *shared_video, bool is_sequenc srs_assert(c); // when got video stream info. - SrsStatistic *stat = _srs_stat; - if (c->id_ == SrsVideoCodecIdAVC) { - err = stat->on_video_info(req_, c->id_, c->avc_profile_, c->avc_level_, c->width_, c->height_); + err = stat_->on_video_info(req_, c->id_, c->avc_profile_, c->avc_level_, c->width_, c->height_); srs_trace("%dB video sh, codec(%d, profile=%s, level=%s, %dx%d, %dkbps, %.1ffps, %.1fs)", msg->size(), c->id_, srs_avc_profile2str(c->avc_profile_).c_str(), srs_avc_level2str(c->avc_level_).c_str(), c->width_, c->height_, c->video_data_rate_ / 1000, c->frame_rate_, c->duration_); } else if (c->id_ == SrsVideoCodecIdHEVC) { - err = stat->on_video_info(req_, c->id_, c->hevc_profile_, c->hevc_level_, c->width_, c->height_); + err = stat_->on_video_info(req_, c->id_, c->hevc_profile_, c->hevc_level_, c->width_, c->height_); srs_trace("%dB video sh, codec(%d, profile=%s, level=%s, %dx%d, %dkbps, %.1ffps, %.1fs)", msg->size(), c->id_, srs_hevc_profile2str(c->hevc_profile_).c_str(), srs_hevc_level2str(c->hevc_level_).c_str(), c->width_, c->height_, c->video_data_rate_ / 1000, c->frame_rate_, c->duration_); @@ -1069,13 +1086,13 @@ srs_error_t SrsOriginHub::on_video(SrsMediaPacket *shared_video, bool is_sequenc if ((err = hls_->on_video(msg, format)) != srs_success) { // TODO: We should support more strategies. // apply the error strategy for hls. - std::string hls_error_strategy = _srs_config->get_hls_on_error(req_->vhost_); + std::string hls_error_strategy = config_->get_hls_on_error(req_->vhost_); if (srs_config_hls_is_on_error_ignore(hls_error_strategy)) { srs_warn("hls: ignore video error %s", srs_error_desc(err).c_str()); hls_->on_unpublish(); srs_freep(err); } else if (srs_config_hls_is_on_error_continue(hls_error_strategy)) { - if (srs_hls_can_continue(srs_error_code(err), source_->meta_->vsh(), msg)) { + if (srs_hls_can_continue(srs_error_code(err), source_->meta()->vsh(), msg)) { srs_freep(err); } else { return srs_error_wrap(err, "hls: video"); @@ -1107,9 +1124,9 @@ srs_error_t SrsOriginHub::on_video(SrsMediaPacket *shared_video, bool is_sequenc // copy to all forwarders. if (!forwarders_.empty()) { - std::vector::iterator it; + std::vector::iterator it; for (it = forwarders_.begin(); it != forwarders_.end(); ++it) { - SrsForwarder *forwarder = *it; + ISrsForwarder *forwarder = *it; if ((err = forwarder->on_video(msg)) != srs_success) { return srs_error_wrap(err, "forward video"); } @@ -1186,9 +1203,9 @@ srs_error_t SrsOriginHub::on_forwarder_start(SrsForwarder *forwarder) { srs_error_t err = srs_success; - SrsMediaPacket *cache_metadata = source_->meta_->data(); - SrsMediaPacket *cache_sh_video = source_->meta_->vsh(); - SrsMediaPacket *cache_sh_audio = source_->meta_->ash(); + SrsMediaPacket *cache_metadata = source_->meta()->data(); + SrsMediaPacket *cache_sh_video = source_->meta()->vsh(); + SrsMediaPacket *cache_sh_audio = source_->meta()->ash(); // feed the forwarder the metadata/sequence header, // when reload to enable the forwarder. @@ -1209,9 +1226,9 @@ srs_error_t SrsOriginHub::on_dvr_request_sh() { srs_error_t err = srs_success; - SrsMediaPacket *cache_metadata = source_->meta_->data(); - SrsMediaPacket *cache_sh_video = source_->meta_->vsh(); - SrsMediaPacket *cache_sh_audio = source_->meta_->ash(); + SrsMediaPacket *cache_metadata = source_->meta()->data(); + SrsMediaPacket *cache_sh_video = source_->meta()->vsh(); + SrsMediaPacket *cache_sh_audio = source_->meta()->ash(); // feed the dvr the metadata/sequence header, // when reload to start dvr, dvr will never get the sequence header in stream, @@ -1221,13 +1238,13 @@ srs_error_t SrsOriginHub::on_dvr_request_sh() } if (cache_sh_video) { - if ((err = dvr_->on_video(cache_sh_video, source_->meta_->vsh_format())) != srs_success) { + if ((err = dvr_->on_video(cache_sh_video, source_->meta()->vsh_format())) != srs_success) { return srs_error_wrap(err, "dvr video"); } } if (cache_sh_audio) { - if ((err = dvr_->on_audio(cache_sh_audio, source_->meta_->ash_format())) != srs_success) { + if ((err = dvr_->on_audio(cache_sh_audio, source_->meta()->ash_format())) != srs_success) { return srs_error_wrap(err, "dvr audio"); } } @@ -1239,16 +1256,16 @@ srs_error_t SrsOriginHub::on_hls_request_sh() { srs_error_t err = srs_success; - SrsMediaPacket *cache_sh_video = source_->meta_->vsh(); + SrsMediaPacket *cache_sh_video = source_->meta()->vsh(); if (cache_sh_video) { - if ((err = hls_->on_video(cache_sh_video, source_->meta_->vsh_format())) != srs_success) { + if ((err = hls_->on_video(cache_sh_video, source_->meta()->vsh_format())) != srs_success) { return srs_error_wrap(err, "hls video"); } } - SrsMediaPacket *cache_sh_audio = source_->meta_->ash(); + SrsMediaPacket *cache_sh_audio = source_->meta()->ash(); if (cache_sh_audio) { - if ((err = hls_->on_audio(cache_sh_audio, source_->meta_->ash_format())) != srs_success) { + if ((err = hls_->on_audio(cache_sh_audio, source_->meta()->ash_format())) != srs_success) { return srs_error_wrap(err, "hls audio"); } } @@ -1260,7 +1277,7 @@ srs_error_t SrsOriginHub::create_forwarders() { srs_error_t err = srs_success; - if (!_srs_config->get_forward_enabled(req_->vhost_)) { + if (!config_->get_forward_enabled(req_->vhost_)) { return err; } @@ -1277,11 +1294,11 @@ srs_error_t SrsOriginHub::create_forwarders() } // For destanition config - SrsConfDirective *conf = _srs_config->get_forwards(req_->vhost_); + SrsConfDirective *conf = config_->get_forwards(req_->vhost_); for (int i = 0; conf && i < (int)conf->args_.size(); i++) { std::string forward_server = conf->args_.at(i); - SrsForwarder *forwarder = new SrsForwarder(this); + ISrsForwarder *forwarder = new SrsForwarder(this); forwarders_.push_back(forwarder); // initialize the forwarder with request. @@ -1289,7 +1306,7 @@ srs_error_t SrsOriginHub::create_forwarders() return srs_error_wrap(err, "init forwarder"); } - srs_utime_t queue_size = _srs_config->get_queue_length(req_->vhost_); + srs_utime_t queue_size = config_->get_queue_length(req_->vhost_); forwarder->set_queue_size(queue_size); if ((err = forwarder->on_publish()) != srs_success) { @@ -1308,7 +1325,7 @@ srs_error_t SrsOriginHub::create_backend_forwarders(bool &applied) // default not configure backend service applied = false; - SrsConfDirective *conf = _srs_config->get_forward_backend(req_->vhost_); + SrsConfDirective *conf = config_->get_forward_backend(req_->vhost_); if (!conf || conf->arg0().empty()) { return err; } @@ -1321,7 +1338,7 @@ srs_error_t SrsOriginHub::create_backend_forwarders(bool &applied) // get urls on forward backend std::vector urls; - if ((err = _srs_hooks->on_forward_backend(backend_url, req_, urls)) != srs_success) { + if ((err = hooks_->on_forward_backend(backend_url, req_, urls)) != srs_success) { return srs_error_wrap(err, "get forward backend failed, backend=%s", backend_url.c_str()); } @@ -1336,7 +1353,7 @@ srs_error_t SrsOriginHub::create_backend_forwarders(bool &applied) srs_net_url_parse_tcurl(req->tcUrl_, req->schema_, req->host_, req->vhost_, req->app_, req->stream_, req->port_, req->param_); // create forwarder - SrsForwarder *forwarder = new SrsForwarder(this); + ISrsForwarder *forwarder = new SrsForwarder(this); forwarders_.push_back(forwarder); std::stringstream forward_server; @@ -1347,7 +1364,7 @@ srs_error_t SrsOriginHub::create_backend_forwarders(bool &applied) return srs_error_wrap(err, "init backend forwarder failed, forward-to=%s", forward_server.str().c_str()); } - srs_utime_t queue_size = _srs_config->get_queue_length(req_->vhost_); + srs_utime_t queue_size = config_->get_queue_length(req_->vhost_); forwarder->set_queue_size(queue_size); if ((err = forwarder->on_publish()) != srs_success) { @@ -1361,9 +1378,9 @@ srs_error_t SrsOriginHub::create_backend_forwarders(bool &applied) void SrsOriginHub::destroy_forwarders() { - std::vector::iterator it; + std::vector::iterator it; for (it = forwarders_.begin(); it != forwarders_.end(); ++it) { - SrsForwarder *forwarder = *it; + ISrsForwarder *forwarder = *it; forwarder->on_unpublish(); srs_freep(forwarder); } @@ -1564,12 +1581,16 @@ SrsLiveSourceManager::SrsLiveSourceManager() { lock_ = srs_mutex_new(); timer_ = new SrsHourGlass("sources", this, 1 * SRS_UTIME_SECONDS); + + app_factory_ = _srs_app_factory; } SrsLiveSourceManager::~SrsLiveSourceManager() { srs_mutex_destroy(lock_); srs_freep(timer_); + + app_factory_ = NULL; } srs_error_t SrsLiveSourceManager::initialize() @@ -1596,7 +1617,7 @@ srs_error_t SrsLiveSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr &source = it->second; pps = source; } else { - SrsSharedPtr source = new SrsLiveSource(); + SrsSharedPtr source = app_factory_->create_live_source(); srs_trace("new live source, stream_url=%s", stream_url.c_str()); pps = source; @@ -1703,6 +1724,14 @@ void SrsLiveSourceManager::destroy() pool_.clear(); } +ISrsLiveSource::ISrsLiveSource() +{ +} + +ISrsLiveSource::~ISrsLiveSource() +{ +} + SrsLiveSource::SrsLiveSource() { req_ = NULL; @@ -1726,13 +1755,22 @@ SrsLiveSource::SrsLiveSource() is_monotonically_increase_ = false; last_packet_time_ = 0; - _srs_config->subscribe(this); atc_ = false; + + config_ = _srs_config; + stat_ = _srs_stat; + handler_ = _srs_server; + app_factory_ = _srs_app_factory; +} + +void SrsLiveSource::assemble() +{ + config_->subscribe(this); } SrsLiveSource::~SrsLiveSource() { - _srs_config->unsubscribe(this); + config_->unsubscribe(this); // never free the consumers, // for all consumers are auto free. @@ -1754,6 +1792,11 @@ SrsLiveSource::~SrsLiveSource() if (cid.empty()) cid = _pre_source_id; srs_trace("free live source id=[%s]", cid.c_str()); + + config_ = NULL; + stat_ = NULL; + handler_ = NULL; + app_factory_ = NULL; } void SrsLiveSource::dispose() @@ -1815,6 +1858,16 @@ bool SrsLiveSource::publisher_is_idle_for(srs_utime_t timeout) return false; } +SrsMetaCache *SrsLiveSource::meta() +{ + return meta_; +} + +SrsRtmpFormat *SrsLiveSource::format() +{ + return format_; +} + // CRITICAL: This method is called AFTER the source has been added to the source pool // in the fetch_or_create pattern (see PR 4449). // @@ -1834,17 +1887,17 @@ srs_error_t SrsLiveSource::initialize(SrsSharedPtr wrapper, ISrsR srs_assert(!req_); req_ = r->copy(); - atc_ = _srs_config->get_atc(req_->vhost_); + atc_ = config_->get_atc(req_->vhost_); - jitter_algorithm_ = (SrsRtmpJitterAlgorithm)_srs_config->get_time_jitter(req_->vhost_); - mix_correct_ = _srs_config->get_mix_correct(req_->vhost_); + jitter_algorithm_ = (SrsRtmpJitterAlgorithm)config_->get_time_jitter(req_->vhost_); + mix_correct_ = config_->get_mix_correct(req_->vhost_); if ((err = format_->initialize()) != srs_success) { return srs_error_wrap(err, "format initialize"); } // Setup the SPS/PPS parsing strategy. - format_->try_annexb_first_ = _srs_config->try_annexb_first(r->vhost_); + format_->try_annexb_first_ = config_->try_annexb_first(r->vhost_); if ((err = play_edge_->initialize(wrapper, req_)) != srs_success) { return srs_error_wrap(err, "edge(play)"); @@ -1853,14 +1906,14 @@ srs_error_t SrsLiveSource::initialize(SrsSharedPtr wrapper, ISrsR return srs_error_wrap(err, "edge(publish)"); } - srs_utime_t queue_size = _srs_config->get_queue_length(req_->vhost_); + srs_utime_t queue_size = config_->get_queue_length(req_->vhost_); publish_edge_->set_queue_size(queue_size); // Create and initialize origin hub only for origin servers, not edge servers - bool edge = _srs_config->get_vhost_is_edge(req_->vhost_); + bool edge = config_->get_vhost_is_edge(req_->vhost_); if (!edge) { srs_freep(hub_); - hub_ = new SrsOriginHub(); + hub_ = app_factory_->create_origin_hub(); } else { srs_warn("disable OriginHub creation for edge vhost=%s", req_->vhost_.c_str()); } @@ -1942,8 +1995,8 @@ srs_error_t SrsLiveSource::on_meta_data(SrsRtmpCommonMessage *msg, SrsOnMetaData // if allow atc_auto and bravo-atc detected, open atc for vhost. SrsAmf0Any *prop = NULL; - atc_ = _srs_config->get_atc(req_->vhost_); - if (_srs_config->get_atc_auto(req_->vhost_)) { + atc_ = config_->get_atc(req_->vhost_); + if (config_->get_atc_auto(req_->vhost_)) { if ((prop = metadata->metadata_->get_property("bravo_atc")) != NULL) { if (prop->is_string() && prop->to_str() == "true") { atc_ = true; @@ -1962,7 +2015,7 @@ srs_error_t SrsLiveSource::on_meta_data(SrsRtmpCommonMessage *msg, SrsOnMetaData // when already got metadata, drop when reduce sequence header. bool drop_for_reduce = false; - if (meta_->data() && _srs_config->get_reduce_sequence_header(req_->vhost_)) { + if (meta_->data() && config_->get_reduce_sequence_header(req_->vhost_)) { drop_for_reduce = true; srs_warn("drop for reduce sh metadata, size=%d", msg->size()); } @@ -2060,7 +2113,7 @@ srs_error_t SrsLiveSource::on_audio_imp(SrsMediaPacket *msg) // whether consumer should drop for the duplicated sequence header. bool drop_for_reduce = false; - if (is_sequence_header && meta_->previous_ash() && _srs_config->get_reduce_sequence_header(req_->vhost_)) { + if (is_sequence_header && meta_->previous_ash() && config_->get_reduce_sequence_header(req_->vhost_)) { if (meta_->previous_ash()->size() == msg->size()) { drop_for_reduce = srs_bytes_equal(meta_->previous_ash()->payload(), msg->payload(), msg->size()); srs_warn("drop for reduce sh audio, size=%d", msg->size()); @@ -2160,7 +2213,7 @@ srs_error_t SrsLiveSource::on_video_imp(SrsMediaPacket *msg) // user can disable the sps parse to workaround when parse sps failed. // @see https://github.com/ossrs/srs/issues/474 if (is_sequence_header) { - format_->avc_parse_sps_ = _srs_config->get_parse_sps(req_->vhost_); + format_->avc_parse_sps_ = config_->get_parse_sps(req_->vhost_); } if ((err = format_->on_video(msg)) != srs_success) { @@ -2175,7 +2228,7 @@ srs_error_t SrsLiveSource::on_video_imp(SrsMediaPacket *msg) // whether consumer should drop for the duplicated sequence header. bool drop_for_reduce = false; - if (is_sequence_header && meta_->previous_vsh() && _srs_config->get_reduce_sequence_header(req_->vhost_)) { + if (is_sequence_header && meta_->previous_vsh() && config_->get_reduce_sequence_header(req_->vhost_)) { if (meta_->previous_vsh()->size() == msg->size()) { drop_for_reduce = srs_bytes_equal(meta_->previous_vsh()->payload(), msg->payload(), msg->size()); srs_warn("drop for reduce sh video, size=%d", msg->size()); @@ -2350,9 +2403,8 @@ srs_error_t SrsLiveSource::on_publish() } // notify the handler. - ISrsLiveSourceHandler *handler = _srs_server; - srs_assert(handler); - if ((err = handler->on_publish(req_)) != srs_success) { + srs_assert(handler_); + if ((err = handler_->on_publish(req_)) != srs_success) { return srs_error_wrap(err, "handle publish"); } @@ -2360,8 +2412,7 @@ srs_error_t SrsLiveSource::on_publish() return srs_error_wrap(err, "bridge publish"); } - SrsStatistic *stat = _srs_stat; - stat->on_stream_publish(req_, _source_id.c_str()); + stat_->on_stream_publish(req_, _source_id.c_str()); // When no players, the publisher is idle now. if (consumers_.empty()) { @@ -2401,13 +2452,10 @@ void SrsLiveSource::on_unpublish() _source_id = SrsContextId(); // notify the handler. - ISrsLiveSourceHandler *handler = _srs_server; - srs_assert(handler); + stat_->on_stream_close(req_); - SrsStatistic *stat = _srs_stat; - stat->on_stream_close(req_); - - handler->on_unpublish(req_); + srs_assert(handler_); + handler_->on_unpublish(req_); if (rtmp_bridge_) { rtmp_bridge_->on_unpublish(); @@ -2430,7 +2478,7 @@ srs_error_t SrsLiveSource::create_consumer(SrsLiveConsumer *&consumer) srs_error_t err = srs_success; // for edge, when play edge stream, check the state - if (_srs_config->get_vhost_is_edge(req_->vhost_)) { + if (config_->get_vhost_is_edge(req_->vhost_)) { // notice edge to start for the first client. if ((err = play_edge_->on_client_play()) != srs_success) { return srs_error_wrap(err, "play edge"); @@ -2451,7 +2499,7 @@ srs_error_t SrsLiveSource::consumer_dumps(SrsLiveConsumer *consumer, bool ds, bo { srs_error_t err = srs_success; - srs_utime_t queue_size = _srs_config->get_queue_length(req_->vhost_); + srs_utime_t queue_size = config_->get_queue_length(req_->vhost_); consumer->set_queue_size(queue_size); // if atc, update the sequence header to gop cache time. @@ -2509,7 +2557,7 @@ void SrsLiveSource::on_consumer_destroy(SrsLiveConsumer *consumer) // For edge server, the stream die when the last player quit, because the edge stream is created by player // activities, so it should die when all players quit. - if (_srs_config->get_vhost_is_edge(req_->vhost_)) { + if (config_->get_vhost_is_edge(req_->vhost_)) { stream_die_at_ = srs_time_now_cached(); } diff --git a/trunk/src/app/srs_app_rtmp_source.hpp b/trunk/src/app/srs_app_rtmp_source.hpp index 6da9df7a9..1fa40bbfd 100644 --- a/trunk/src/app/srs_app_rtmp_source.hpp +++ b/trunk/src/app/srs_app_rtmp_source.hpp @@ -47,6 +47,20 @@ class SrsBuffer; #ifdef SRS_HDS class SrsHds; #endif +class ISrsStatistic; +class ISrsHttpHooks; +class ISrsAppConfig; +class ISrsLiveSource; +class ISrsHls; +class ISrsDash; +class ISrsDvr; +class ISrsMediaEncoder; +#ifdef SRS_HDS +class ISrsHds; +#endif +class ISrsNgExec; +class ISrsForwarder; +class SrsAppFactory; // The time jitter algorithm: // 1. full, to ensure stream start at zero, and ensure stream monotonically increasing. @@ -180,7 +194,7 @@ class SrsLiveConsumer : public ISrsWakable { private: // Because source references to this object, so we should directly use the source ptr. - SrsLiveSource *source_; + ISrsLiveSource *source_; private: SrsRtmpJitter *jitter_; @@ -196,7 +210,7 @@ private: srs_utime_t mw_duration_; #endif public: - SrsLiveConsumer(SrsLiveSource *s); + SrsLiveConsumer(ISrsLiveSource *s); virtual ~SrsLiveConsumer(); public: @@ -329,14 +343,48 @@ public: virtual SrsMediaPacket *pop(); }; +// The interface for origin hub. +class ISrsOriginHub +{ +public: + ISrsOriginHub(); + virtual ~ISrsOriginHub(); + +public: + virtual srs_error_t initialize(SrsSharedPtr s, ISrsRequest *r) = 0; + virtual void dispose() = 0; + virtual srs_error_t cycle() = 0; + virtual bool active() = 0; + virtual srs_utime_t cleanup_delay() = 0; + +public: + // When got a parsed metadata. + virtual srs_error_t on_meta_data(SrsMediaPacket *shared_metadata, SrsOnMetaDataPacket *packet) = 0; + // When got a parsed audio packet. + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio) = 0; + // When got a parsed video packet. + virtual srs_error_t on_video(SrsMediaPacket *shared_video, bool is_sequence_header) = 0; + +public: + // When start publish stream. + virtual srs_error_t on_publish() = 0; + // When stop publish stream. + virtual void on_unpublish() = 0; +}; + // The hub for origin is a collection of utilities for origin only, // For example, DVR, HLS, Forward and Transcode are only available for origin, // they are meanless for edge server. -class SrsOriginHub : public ISrsReloadHandler +class SrsOriginHub : public ISrsReloadHandler, public ISrsOriginHub { +private: + ISrsAppConfig *config_; + ISrsStatistic *stat_; + ISrsHttpHooks *hooks_; + private: // Because source references to this object, so we should directly use the source ptr. - SrsLiveSource *source_; + ISrsLiveSource *source_; private: ISrsRequest *req_; @@ -344,24 +392,25 @@ private: private: // hls handler. - SrsHls *hls_; + ISrsHls *hls_; // The DASH encoder. - SrsDash *dash_; + ISrsDash *dash_; // dvr handler. - SrsDvr *dvr_; + ISrsDvr *dvr_; // transcoding handler. - SrsEncoder *encoder_; + ISrsMediaEncoder *encoder_; #ifdef SRS_HDS // adobe hds(http dynamic streaming). - SrsHds *hds_; + ISrsHds *hds_; #endif // nginx-rtmp exec feature. - SrsNgExec *ng_exec_; + ISrsNgExec *ng_exec_; // To forward stream to other servers - std::vector forwarders_; + std::vector forwarders_; public: SrsOriginHub(); + void assemble(); virtual ~SrsOriginHub(); public: @@ -483,12 +532,15 @@ public: }; // The source manager to create and refresh all stream sources. -class SrsLiveSourceManager : public ISrsHourGlass, public ISrsLiveSourceManager +class SrsLiveSourceManager : public ISrsHourGlassHandler, public ISrsLiveSourceManager { +private: + SrsAppFactory *app_factory_; + private: srs_mutex_t lock_; std::map > pool_; - SrsHourGlass *timer_; + ISrsHourGlass *timer_; public: SrsLiveSourceManager(); @@ -509,7 +561,7 @@ public: public: // dispose and cycle all sources. virtual void dispose(); - // interface ISrsHourGlass + // interface ISrsHourGlassHandler private: virtual srs_error_t setup_ticks(); virtual srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick); @@ -523,10 +575,29 @@ public: // Global singleton instance. extern SrsLiveSourceManager *_srs_sources; -// The live streaming source. -class SrsLiveSource : public ISrsReloadHandler, public ISrsFrameTarget +// The live source interface. +class ISrsLiveSource { - friend class SrsOriginHub; +public: + ISrsLiveSource(); + virtual ~ISrsLiveSource(); + +public: + virtual void on_consumer_destroy(SrsLiveConsumer *consumer) = 0; + virtual SrsContextId source_id() = 0; + virtual SrsContextId pre_source_id() = 0; + virtual SrsMetaCache *meta() = 0; + virtual SrsRtmpFormat *format() = 0; +}; + +// The live streaming source. +class SrsLiveSource : public ISrsReloadHandler, public ISrsFrameTarget, public ISrsLiveSource +{ +private: + ISrsAppConfig *config_; + ISrsStatistic *stat_; + ISrsLiveSourceHandler *handler_; + SrsAppFactory *app_factory_; private: // For publish, it's the publish client id. @@ -563,7 +634,7 @@ private: // The gop cache for client fast startup. SrsGopCache *gop_cache_; // The hub for origin server. - SrsOriginHub *hub_; + ISrsOriginHub *hub_; // The metadata cache. SrsMetaCache *meta_; // The format, codec information. @@ -579,6 +650,7 @@ private: public: SrsLiveSource(); + void assemble(); virtual ~SrsLiveSource(); public: @@ -588,6 +660,8 @@ public: virtual bool stream_is_dead(); // Whether publisher is idle for a period of timeout. bool publisher_is_idle_for(srs_utime_t timeout); + virtual SrsMetaCache *meta(); + virtual SrsRtmpFormat *format(); public: // Initialize the hls with handlers. diff --git a/trunk/src/app/srs_app_rtsp_conn.cpp b/trunk/src/app/srs_app_rtsp_conn.cpp index 29f48bdb2..507b66aec 100644 --- a/trunk/src/app/srs_app_rtsp_conn.cpp +++ b/trunk/src/app/srs_app_rtsp_conn.cpp @@ -549,7 +549,8 @@ srs_error_t SrsRtspConnection::do_cycle() } else if (req->is_describe()) { // create session. if (session_id_.empty()) { - session_id_ = srs_rand_gen_str(8); + SrsRand rand; + session_id_ = rand.gen_str(8); } SrsUniquePtr res(new SrsRtspDescribeResponse((int)req->seq_)); diff --git a/trunk/src/app/srs_app_rtsp_source.cpp b/trunk/src/app/srs_app_rtsp_source.cpp index dddd9e0fa..3058cde5a 100644 --- a/trunk/src/app/srs_app_rtsp_source.cpp +++ b/trunk/src/app/srs_app_rtsp_source.cpp @@ -539,10 +539,12 @@ srs_error_t SrsRtspRtpBuilder::initialize_audio_track(SrsAudioCodecId codec) // RTSP behavior: Build track description from real audio format, not default values // This is different from RTC which uses default track descriptions + SrsRand rand; + // Create audio track description from actual format data SrsUniquePtr audio_desc(new SrsRtcTrackDescription()); audio_desc->type_ = "audio"; - audio_desc->id_ = "audio-" + srs_rand_gen_str(8); + audio_desc->id_ = "audio-" + rand.gen_str(8); audio_desc->direction_ = "recvonly"; // Generate SSRC for this track @@ -612,10 +614,12 @@ srs_error_t SrsRtspRtpBuilder::initialize_video_track(SrsVideoCodecId codec) std::string codec_name = srs_video_codec_id2str(codec); + SrsRand rand; + // Create video track description from actual format data SrsUniquePtr video_desc(new SrsRtcTrackDescription()); video_desc->type_ = "video"; - video_desc->id_ = "video-" + codec_name + "-" + srs_rand_gen_str(8); + video_desc->id_ = "video-" + codec_name + "-" + rand.gen_str(8); video_desc->direction_ = "recvonly"; // Generate SSRC for this track diff --git a/trunk/src/app/srs_app_rtsp_source.hpp b/trunk/src/app/srs_app_rtsp_source.hpp index aec6cb850..1aad6dc1d 100644 --- a/trunk/src/app/srs_app_rtsp_source.hpp +++ b/trunk/src/app/srs_app_rtsp_source.hpp @@ -67,7 +67,7 @@ public: void on_stream_change(SrsRtcSourceDescription *desc); }; -class SrsRtspSourceManager : public ISrsHourGlass +class SrsRtspSourceManager : public ISrsHourGlassHandler { private: srs_mutex_t lock_; @@ -80,7 +80,7 @@ public: public: virtual srs_error_t initialize(); - // interface ISrsHourGlass + // interface ISrsHourGlassHandler private: virtual srs_error_t setup_ticks(); virtual srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick); diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index 2fc09ba39..06cc1d015 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -132,7 +132,11 @@ srs_error_t srs_global_initialize() _srs_reload_err = srs_success; _srs_reload_state = SrsReloadStateInit; - _srs_reload_id = srs_rand_gen_str(7); + SrsRand rand; + _srs_reload_id = rand.gen_str(7); + + // Initialize global statistic instance. + _srs_stat = new SrsStatistic(); return err; } @@ -178,11 +182,10 @@ SrsServer::SrsServer() ingester_ = new SrsIngester(); timer_ = NULL; - // Initialize global statistic instance. - _srs_stat = new SrsStatistic(); - // Initialize WebRTC components rtc_session_manager_ = new SrsRtcSessionManager(); + + config_ = _srs_config; } SrsServer::~SrsServer() @@ -234,13 +237,12 @@ SrsServer::~SrsServer() srs_freep(rtc_session_manager_); - // Cleanup global statistic instance. - srs_freep(_srs_stat); + config_ = NULL; } void SrsServer::dispose() { - _srs_config->unsubscribe(this); + config_->unsubscribe(this); // Destroy all listeners. rtmp_listener_->close(); @@ -272,11 +274,11 @@ void SrsServer::dispose() void SrsServer::gracefully_dispose() { - _srs_config->unsubscribe(this); + config_->unsubscribe(this); // Always wait for a while to start. - srs_usleep(_srs_config->get_grace_start_wait()); - srs_trace("start wait for %dms", srsu2msi(_srs_config->get_grace_start_wait())); + srs_usleep(config_->get_grace_start_wait()); + srs_trace("start wait for %dms", srsu2msi(config_->get_grace_start_wait())); // Destroy all listeners. rtmp_listener_->close(); @@ -318,8 +320,8 @@ void SrsServer::gracefully_dispose() _srs_sources->dispose(); srs_trace("source disposed"); - srs_usleep(_srs_config->get_grace_final_wait()); - srs_trace("final wait for %dms", srsu2msi(_srs_config->get_grace_final_wait())); + srs_usleep(config_->get_grace_final_wait()); + srs_trace("final wait for %dms", srsu2msi(config_->get_grace_final_wait())); } ISrsHttpServeMux *SrsServer::api_server() @@ -365,16 +367,16 @@ srs_error_t SrsServer::initialize() // for the main objects(server, config, log, context), // never subscribe handler in constructor, // instead, subscribe handler in initialize method. - srs_assert(_srs_config); - _srs_config->subscribe(this); + srs_assert(config_); + config_->subscribe(this); - bool stream = _srs_config->get_http_stream_enabled(); - vector http_listens = _srs_config->get_http_stream_listens(); - vector https_listens = _srs_config->get_https_stream_listens(); + bool stream = config_->get_http_stream_enabled(); + vector http_listens = config_->get_http_stream_listens(); + vector https_listens = config_->get_https_stream_listens(); - bool rtc = _srs_config->get_rtc_server_enabled(); - bool rtc_tcp = _srs_config->get_rtc_server_tcp_enabled(); - vector rtc_listens = _srs_config->get_rtc_server_tcp_listens(); + bool rtc = config_->get_rtc_server_enabled(); + bool rtc_tcp = config_->get_rtc_server_tcp_enabled(); + vector rtc_listens = config_->get_rtc_server_tcp_listens(); // If enabled and listen is the same value, resue port for WebRTC over TCP. if (stream && rtc && rtc_tcp && srs_strings_equal(http_listens, rtc_listens)) { srs_trace("WebRTC tcp=%s reuses http=%s server", srs_strings_join(rtc_listens, ",").c_str(), srs_strings_join(http_listens, ",").c_str()); @@ -386,9 +388,9 @@ srs_error_t SrsServer::initialize() } // If enabled and the listen is the same value, reuse port. - bool api = _srs_config->get_http_api_enabled(); - vector api_listens = _srs_config->get_http_api_listens(); - vector apis_listens = _srs_config->get_https_api_listens(); + bool api = config_->get_http_api_enabled(); + vector api_listens = config_->get_http_api_listens(); + vector apis_listens = config_->get_https_api_listens(); if (stream && api && srs_strings_equal(api_listens, http_listens) && srs_strings_equal(apis_listens, https_listens)) { srs_trace("API reuses http=%s and https=%s server", srs_strings_join(http_listens, ",").c_str(), srs_strings_join(https_listens, ",").c_str()); reuse_api_over_server_ = true; @@ -494,7 +496,7 @@ srs_error_t SrsServer::initialize_st() srs_error_t err = srs_success; // check asprocess. - bool asprocess = _srs_config->get_asprocess(); + bool asprocess = config_->get_asprocess(); if (asprocess && ppid_ == 1) { return srs_error_new(ERROR_SYSTEM_ASSERT_FAILED, "ppid=%d illegal for asprocess", ppid_); } @@ -526,26 +528,26 @@ srs_error_t SrsServer::listen() srs_error_t err = srs_success; // Create RTMP listeners. - rtmp_listener_->add(_srs_config->get_listens())->set_label("RTMP"); + rtmp_listener_->add(config_->get_listens())->set_label("RTMP"); if ((err = rtmp_listener_->listen()) != srs_success) { return srs_error_wrap(err, "rtmp listen"); } // Create RTMPS listeners. - if (_srs_config->get_rtmps_enabled()) { - rtmps_listener_->add(_srs_config->get_rtmps_listen())->set_label("RTMPS"); + if (config_->get_rtmps_enabled()) { + rtmps_listener_->add(config_->get_rtmps_listen())->set_label("RTMPS"); if ((err = rtmps_listener_->listen()) != srs_success) { return srs_error_wrap(err, "rtmps listen"); } } // Create HTTP API listener. - if (_srs_config->get_http_api_enabled()) { + if (config_->get_http_api_enabled()) { if (reuse_api_over_server_) { - vector listens = _srs_config->get_http_stream_listens(); + vector listens = config_->get_http_stream_listens(); srs_trace("HTTP-API: Reuse listen to http server %s", srs_strings_join(listens, ",").c_str()); } else { - api_listener_->add(_srs_config->get_http_api_listens())->set_label("HTTP-API"); + api_listener_->add(config_->get_http_api_listens())->set_label("HTTP-API"); if ((err = api_listener_->listen()) != srs_success) { return srs_error_wrap(err, "http api listen"); } @@ -553,12 +555,12 @@ srs_error_t SrsServer::listen() } // Create HTTPS API listener. - if (_srs_config->get_https_api_enabled()) { + if (config_->get_https_api_enabled()) { if (reuse_api_over_server_) { - vector listens = _srs_config->get_https_stream_listens(); + vector listens = config_->get_https_stream_listens(); srs_trace("HTTPS-API: Reuse listen to http server %s", srs_strings_join(listens, ",").c_str()); } else { - apis_listener_->add(_srs_config->get_https_api_listens())->set_label("HTTPS-API"); + apis_listener_->add(config_->get_https_api_listens())->set_label("HTTPS-API"); if ((err = apis_listener_->listen()) != srs_success) { return srs_error_wrap(err, "https api listen"); } @@ -566,25 +568,25 @@ srs_error_t SrsServer::listen() } // Create HTTP server listener. - if (_srs_config->get_http_stream_enabled()) { - http_listener_->add(_srs_config->get_http_stream_listens())->set_label("HTTP-Server"); + if (config_->get_http_stream_enabled()) { + http_listener_->add(config_->get_http_stream_listens())->set_label("HTTP-Server"); if ((err = http_listener_->listen()) != srs_success) { return srs_error_wrap(err, "http server listen"); } } // Create HTTPS server listener. - if (_srs_config->get_https_stream_enabled()) { - https_listener_->add(_srs_config->get_https_stream_listens())->set_label("HTTPS-Server"); + if (config_->get_https_stream_enabled()) { + https_listener_->add(config_->get_https_stream_listens())->set_label("HTTPS-Server"); if ((err = https_listener_->listen()) != srs_success) { return srs_error_wrap(err, "https server listen"); } } // Start WebRTC over TCP listener. - string protocol = _srs_config->get_rtc_server_protocol(); - if (!reuse_rtc_over_server_ && protocol != "udp" && _srs_config->get_rtc_server_tcp_enabled()) { - webrtc_listener_->add(_srs_config->get_rtc_server_tcp_listens())->set_label("WebRTC"); + string protocol = config_->get_rtc_server_protocol(); + if (!reuse_rtc_over_server_ && protocol != "udp" && config_->get_rtc_server_tcp_enabled()) { + webrtc_listener_->add(config_->get_rtc_server_tcp_listens())->set_label("WebRTC"); if ((err = webrtc_listener_->listen()) != srs_success) { return srs_error_wrap(err, "webrtc tcp listen"); } @@ -592,8 +594,8 @@ srs_error_t SrsServer::listen() #ifdef SRS_RTSP // Start RTSP listener. RTC is a critical dependency. - if (_srs_config->get_rtsp_server_enabled()) { - rtsp_listener_->add(_srs_config->get_rtsp_server_listens())->set_label("RTSP"); + if (config_->get_rtsp_server_enabled()) { + rtsp_listener_->add(config_->get_rtsp_server_listens())->set_label("RTSP"); if ((err = rtsp_listener_->listen()) != srs_success) { return srs_error_wrap(err, "rtsp listen"); } @@ -601,15 +603,15 @@ srs_error_t SrsServer::listen() #endif // Start all listeners for stream caster. - std::vector confs = _srs_config->get_stream_casters(); + std::vector confs = config_->get_stream_casters(); for (vector::iterator it = confs.begin(); it != confs.end(); ++it) { SrsConfDirective *conf = *it; - if (!_srs_config->get_stream_caster_enabled(conf)) { + if (!config_->get_stream_caster_enabled(conf)) { continue; } ISrsListener *listener = NULL; - std::string caster = _srs_config->get_stream_caster_engine(conf); + std::string caster = config_->get_stream_caster_engine(conf); if (srs_stream_caster_is_udp(caster)) { listener = stream_caster_mpegts_; if ((err = stream_caster_mpegts_->initialize(conf)) != srs_success) { @@ -640,8 +642,8 @@ srs_error_t SrsServer::listen() } // Create exporter server listener. - if (_srs_config->get_exporter_enabled()) { - exporter_listener_->set_endpoint(_srs_config->get_exporter_listen())->set_label("Exporter-Server"); + if (config_->get_exporter_enabled()) { + exporter_listener_->set_endpoint(config_->get_exporter_listen())->set_label("Exporter-Server"); if ((err = exporter_listener_->listen()) != srs_success) { return srs_error_wrap(err, "exporter server listen"); } @@ -778,7 +780,7 @@ srs_error_t SrsServer::http_handle() // TODO: FIXME: for console. // TODO: FIXME: support reload. - std::string dir = _srs_config->get_http_stream_dir() + "/console"; + std::string dir = config_->get_http_stream_dir() + "/console"; if ((err = http_api_mux_->handle("/console/", new SrsHttpFileServer(dir))) != srs_success) { return srs_error_wrap(err, "handle console at %s", dir.c_str()); } @@ -900,7 +902,7 @@ void SrsServer::on_signal(int signo) // For K8S, force to gracefully quit for gray release or canary. // @see https://github.com/ossrs/srs/issues/1595#issuecomment-587473037 - if (signo == SRS_SIGNAL_FAST_QUIT && _srs_config->is_force_grace_quit()) { + if (signo == SRS_SIGNAL_FAST_QUIT && config_->is_force_grace_quit()) { srs_trace("force gracefully quit, signo=%d", signo); signo = SRS_SIGNAL_GRACEFULLY_QUIT; } @@ -927,7 +929,7 @@ srs_error_t SrsServer::do_cycle() srs_error_t err = srs_success; // for asprocess. - bool asprocess = _srs_config->get_asprocess(); + bool asprocess = config_->get_asprocess(); while (true) { // asprocess check. @@ -958,7 +960,7 @@ srs_error_t SrsServer::do_cycle() signal_persistence_config_ = false; srs_info("get signal to persistence config to file."); - if ((err = _srs_config->persistence()) != srs_success) { + if ((err = config_->persistence()) != srs_success) { return srs_error_wrap(err, "config persistence to file"); } srs_trace("persistence config to file success."); @@ -972,8 +974,9 @@ srs_error_t SrsServer::do_cycle() SrsReloadState state = SrsReloadStateInit; _srs_reload_state = SrsReloadStateInit; srs_freep(_srs_reload_err); - _srs_reload_id = srs_rand_gen_str(7); - err = _srs_config->reload(&state); + SrsRand rand; + _srs_reload_id = rand.gen_str(7); + err = config_->reload(&state); _srs_reload_state = state; _srs_reload_err = srs_error_copy(err); if (err != srs_success) { @@ -1006,7 +1009,7 @@ srs_error_t SrsServer::setup_ticks() srs_freep(timer_); timer_ = new SrsHourGlass("srs", this, 1 * SRS_UTIME_SECONDS); - if (_srs_config->get_stats_enabled()) { + if (config_->get_stats_enabled()) { if ((err = timer_->tick(2, 3 * SRS_UTIME_SECONDS)) != srs_success) { return srs_error_wrap(err, "tick"); } @@ -1040,8 +1043,8 @@ srs_error_t SrsServer::setup_ticks() } } - if (_srs_config->get_heartbeat_enabled()) { - if ((err = timer_->tick(9, _srs_config->get_heartbeat_interval())) != srs_success) { + if (config_->get_heartbeat_enabled()) { + if ((err = timer_->tick(9, config_->get_heartbeat_interval())) != srs_success) { return srs_error_wrap(err, "tick"); } } @@ -1151,7 +1154,7 @@ srs_error_t SrsServer::listen_srt_mpegts() { srs_error_t err = srs_success; - if (!_srs_config->get_srt_enabled()) { + if (!config_->get_srt_enabled()) { return err; } @@ -1159,7 +1162,7 @@ srs_error_t SrsServer::listen_srt_mpegts() close_srt_listeners(); // Start listeners for SRT, support multiple addresses including IPv6. - vector srt_listens = _srs_config->get_srt_listens(); + vector srt_listens = config_->get_srt_listens(); for (int i = 0; i < (int)srt_listens.size(); i++) { SrsSrtAcceptor *acceptor = new SrsSrtAcceptor(this); @@ -1248,18 +1251,18 @@ srs_error_t SrsServer::listen_rtc_udp() { srs_error_t err = srs_success; - if (!_srs_config->get_rtc_server_enabled()) { + if (!config_->get_rtc_server_enabled()) { return err; } // Check protocol setting - only create UDP listeners if protocol allows UDP - string protocol = _srs_config->get_rtc_server_protocol(); + string protocol = config_->get_rtc_server_protocol(); if (protocol == "tcp") { // When protocol is "tcp", don't create UDP listeners return err; } - vector rtc_listens = _srs_config->get_rtc_server_listens(); + vector rtc_listens = config_->get_rtc_server_listens(); if (rtc_listens.empty()) { return srs_error_new(ERROR_RTC_PORT, "empty rtc listen"); } @@ -1268,7 +1271,7 @@ srs_error_t SrsServer::listen_rtc_udp() srs_assert(rtc_listeners_.empty()); // For each listen address, create multiple listeners based on reuseport setting - int nn_listeners = _srs_config->get_rtc_server_reuseport(); + int nn_listeners = config_->get_rtc_server_reuseport(); for (int j = 0; j < (int)rtc_listens.size(); j++) { string ip; int port; @@ -1397,7 +1400,7 @@ srs_error_t SrsServer::do_on_tcp_client(ISrsListener *listener, srs_netfd_t &stf // Ignore if ip is empty, for example, load balancer keepalive. if (ip.empty()) { - if (_srs_config->empty_ip_ok()) + if (config_->empty_ip_ok()) return err; return srs_error_new(ERROR_SOCKET_GET_PEER_IP, "ignore empty ip, fd=%d", fd); } @@ -1454,8 +1457,8 @@ srs_error_t SrsServer::do_on_tcp_client(ISrsListener *listener, srs_netfd_t &stf if (nn == 10 && b[0] == 0 && b[2] == 0 && b[3] == 1 && b[1] - b[5] == 20 && b[6] == 0x21 && b[7] == 0x12 && b[8] == 0xa4 && b[9] == 0x42) { resource = new SrsRtcTcpConn(io, ip, port); } else { - string key = listener == https_listener_ ? _srs_config->get_https_stream_ssl_key() : ""; - string cert = listener == https_listener_ ? _srs_config->get_https_stream_ssl_cert() : ""; + string key = listener == https_listener_ ? config_->get_https_stream_ssl_key() : ""; + string cert = listener == https_listener_ ? config_->get_https_stream_ssl_cert() : ""; resource = new SrsHttpxConn(_srs_conn_manager, io, http_server_, ip, port, key, cert); } } @@ -1469,12 +1472,12 @@ srs_error_t SrsServer::do_on_tcp_client(ISrsListener *listener, srs_netfd_t &stf SrsRtmpTransport *transport = new SrsRtmpsTransport(stfd2); resource = new SrsRtmpConn(this, transport, ip, port); } else if (listener == api_listener_ || listener == apis_listener_) { - string key = listener == apis_listener_ ? _srs_config->get_https_api_ssl_key() : ""; - string cert = listener == apis_listener_ ? _srs_config->get_https_api_ssl_cert() : ""; + string key = listener == apis_listener_ ? config_->get_https_api_ssl_key() : ""; + string cert = listener == apis_listener_ ? config_->get_https_api_ssl_cert() : ""; resource = new SrsHttpxConn(_srs_conn_manager, new SrsTcpConnection(stfd2), http_api_mux_, ip, port, key, cert); } else if (listener == http_listener_ || listener == https_listener_) { - string key = listener == https_listener_ ? _srs_config->get_https_stream_ssl_key() : ""; - string cert = listener == https_listener_ ? _srs_config->get_https_stream_ssl_cert() : ""; + string key = listener == https_listener_ ? config_->get_https_stream_ssl_key() : ""; + string cert = listener == https_listener_ ? config_->get_https_stream_ssl_cert() : ""; resource = new SrsHttpxConn(_srs_conn_manager, new SrsTcpConnection(stfd2), http_server_, ip, port, key, cert); } else if (listener == webrtc_listener_) { resource = new SrsRtcTcpConn(new SrsTcpConnection(stfd2), ip, port); @@ -1525,7 +1528,7 @@ srs_error_t SrsServer::on_before_connection(const char *label, int fd, const std srs_error_t err = srs_success; // Failed if exceed the connection limitation. - int max_connections = _srs_config->get_max_connections(); + int max_connections = config_->get_max_connections(); if ((int)_srs_conn_manager->size() >= max_connections) { return srs_error_new(ERROR_EXCEED_CONNECTIONS, "drop %s fd=%d, ip=%s:%d, max=%d, cur=%d for exceed connection limits", @@ -1708,8 +1711,8 @@ srs_error_t SrsInotifyWorker::start() #if !defined(SRS_OSX) && !defined(SRS_CYGWIN64) // Whether enable auto reload config. - bool auto_reload = _srs_config->inotify_auto_reload(); - if (!auto_reload && _srs_in_docker && _srs_config->auto_reload_for_docker()) { + bool auto_reload = config_->inotify_auto_reload(); + if (!auto_reload && _srs_in_docker && config_->auto_reload_for_docker()) { srs_warn("enable auto reload for docker"); auto_reload = true; } @@ -1766,7 +1769,8 @@ srs_error_t SrsInotifyWorker::start() // #define IN_ONESHOT 0x80000000 /* only send event once */ // Watch the config directory events. - string config_dir = srs_path_filepath_dir(_srs_config->config()); + SrsPath path_util; + string config_dir = path_util.filepath_dir(config_->config()); uint32_t mask = IN_MODIFY | IN_CREATE | IN_MOVED_TO; int watch_conf = 0; if ((watch_conf = ::inotify_add_watch(fd, config_dir.c_str(), mask)) < 0) { @@ -1788,8 +1792,9 @@ srs_error_t SrsInotifyWorker::cycle() srs_error_t err = srs_success; #if !defined(SRS_OSX) && !defined(SRS_CYGWIN64) - string config_path = _srs_config->config(); - string config_file = srs_path_filepath_base(config_path); + SrsPath path; + string config_path = config_->config(); + string config_file = path.filepath_base(config_path); string k8s_file = "..data"; while (true) { @@ -1821,7 +1826,8 @@ srs_error_t SrsInotifyWorker::cycle() } // Notify server to do reload. - if (do_reload && srs_path_exists(config_path)) { + SrsPath path; + if (do_reload && path.exists(config_path)) { server_->on_signal(SRS_SIGNAL_RELOAD); } @@ -1835,18 +1841,20 @@ srs_error_t SrsInotifyWorker::cycle() SrsPidFileLocker::SrsPidFileLocker() { pid_fd_ = -1; + config_ = _srs_config; } SrsPidFileLocker::~SrsPidFileLocker() { close(); + config_ = NULL; } srs_error_t SrsPidFileLocker::acquire() { srs_error_t err = srs_success; - pid_file_ = _srs_config->get_pid_file(); + pid_file_ = config_->get_pid_file(); // -rw-r--r-- // 644 diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 7392b2200..c75029a62 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -56,6 +56,7 @@ class SrsSrtAcceptor; class SrsSrtEventLoop; class SrsRtcSessionManager; class SrsPidFileLocker; +class ISrsAppConfig; // Initialize global shared variables cross all threads. extern srs_error_t srs_global_initialize(); @@ -66,10 +67,13 @@ extern srs_error_t srs_global_initialize(); class SrsServer : public ISrsReloadHandler, // Reload framework for permormance optimization. public ISrsLiveSourceHandler, public ISrsTcpHandler, - public ISrsHourGlass, + public ISrsHourGlassHandler, public ISrsSrtClientHandler, public ISrsUdpMuxHandler { +private: + ISrsAppConfig *config_; + private: ISrsHttpServeMux *http_api_mux_; SrsHttpServer *http_server_; @@ -207,7 +211,7 @@ private: // the cpu/mem/network statistic. virtual srs_error_t do_cycle(); - // interface ISrsHourGlass + // interface ISrsHourGlassHandler private: virtual srs_error_t setup_ticks(); virtual srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick); @@ -313,6 +317,9 @@ public: // PID file manager for process identification and locking. class SrsPidFileLocker { +private: + ISrsAppConfig *config_; + private: int pid_fd_; std::string pid_file_; diff --git a/trunk/src/app/srs_app_srt_source.hpp b/trunk/src/app/srs_app_srt_source.hpp index 0ec1b282f..2c2c5b3b1 100644 --- a/trunk/src/app/srs_app_srt_source.hpp +++ b/trunk/src/app/srs_app_srt_source.hpp @@ -65,7 +65,7 @@ public: }; // The SRT source manager. -class SrsSrtSourceManager : public ISrsHourGlass, public ISrsSrtSourceManager +class SrsSrtSourceManager : public ISrsHourGlassHandler, public ISrsSrtSourceManager { private: srs_mutex_t lock_; @@ -78,7 +78,7 @@ public: public: virtual srs_error_t initialize(); - // interface ISrsHourGlass + // interface ISrsHourGlassHandler private: virtual srs_error_t setup_ticks(); virtual srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick); diff --git a/trunk/src/app/srs_app_statistic.cpp b/trunk/src/app/srs_app_statistic.cpp index 5517337cc..6e762a039 100644 --- a/trunk/src/app/srs_app_statistic.cpp +++ b/trunk/src/app/srs_app_statistic.cpp @@ -23,7 +23,8 @@ using namespace std; string srs_generate_stat_vid() { - return "vid-" + srs_rand_gen_str(7); + SrsRand rand; + return "vid-" + rand.gen_str(7); } SrsStatisticVhost::SrsStatisticVhost() @@ -567,7 +568,8 @@ std::string SrsStatistic::server_id() std::string SrsStatistic::service_id() { if (service_id_.empty()) { - service_id_ = srs_rand_gen_str(8); + SrsRand rand; + service_id_ = rand.gen_str(8); } return service_id_; diff --git a/trunk/src/app/srs_app_statistic.hpp b/trunk/src/app/srs_app_statistic.hpp index 1452adea8..d285290d6 100644 --- a/trunk/src/app/srs_app_statistic.hpp +++ b/trunk/src/app/srs_app_statistic.hpp @@ -140,6 +140,11 @@ public: public: virtual void on_disconnect(std::string id, srs_error_t err) = 0; virtual srs_error_t on_client(std::string id, ISrsRequest *req, ISrsExpire *conn, SrsRtmpConnType type) = 0; + virtual srs_error_t on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height) = 0; + virtual srs_error_t on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, + SrsAudioChannels asound_type, SrsAacObjectType aac_object) = 0; + virtual void on_stream_publish(ISrsRequest *req, std::string publisher_id) = 0; + virtual void on_stream_close(ISrsRequest *req) = 0; }; // The global statistic instance. diff --git a/trunk/src/app/srs_app_utility.cpp b/trunk/src/app/srs_app_utility.cpp index fd05f7206..e67580e35 100644 --- a/trunk/src/app/srs_app_utility.cpp +++ b/trunk/src/app/srs_app_utility.cpp @@ -154,7 +154,15 @@ string srs_path_build_timestamp(string template_path) return path; } -srs_error_t srs_kill_forced(int &pid) +SrsAppUtility::SrsAppUtility() +{ +} + +SrsAppUtility::~SrsAppUtility() +{ +} + +srs_error_t SrsAppUtility::kill(int &pid) { srs_error_t err = srs_success; @@ -163,7 +171,7 @@ srs_error_t srs_kill_forced(int &pid) } // first, try kill by SIGTERM. - if (kill(pid, SIGTERM) < 0) { + if (::kill(pid, SIGTERM) < 0) { return srs_error_new(ERROR_SYSTEM_KILL, "kill"); } @@ -190,7 +198,7 @@ srs_error_t srs_kill_forced(int &pid) } // then, try kill by SIGKILL. - if (kill(pid, SIGKILL) < 0) { + if (::kill(pid, SIGKILL) < 0) { return srs_error_new(ERROR_SYSTEM_KILL, "kill"); } @@ -1302,7 +1310,8 @@ void srs_api_dump_summaries(SrsJsonObject *obj) n_sample_time = o.sample_time_; // stat the intranet bytes. - if (inter == "lo" || !srs_net_device_is_internet(inter)) { + SrsProtocolUtility utility; + if (inter == "lo" || !utility.is_internet(inter)) { nri_bytes += o.rbytes_; nsi_bytes += o.sbytes_; continue; diff --git a/trunk/src/app/srs_app_utility.hpp b/trunk/src/app/srs_app_utility.hpp index 6d5aad5e6..f59e7abac 100644 --- a/trunk/src/app/srs_app_utility.hpp +++ b/trunk/src/app/srs_app_utility.hpp @@ -50,11 +50,20 @@ extern std::string srs_path_build_stream(std::string template_path, std::string // @return the replaced path. extern std::string srs_path_build_timestamp(std::string template_path); -// Kill the pid by SIGINT, then wait to quit, -// Kill the pid by SIGKILL again when exceed the timeout. -// @param pid the pid to kill. ignore for -1. set to -1 when killed. -// @return an int error code. -extern srs_error_t srs_kill_forced(int &pid); +// The app utility. +class SrsAppUtility +{ +public: + SrsAppUtility(); + virtual ~SrsAppUtility(); + +public: + // Kill the pid by SIGINT, then wait to quit, + // Kill the pid by SIGKILL again when exceed the timeout. + // @param pid the pid to kill. ignore for -1. set to -1 when killed. + // @return an int error code. + virtual srs_error_t kill(int &pid); +}; // Current process resource usage. // @see: man getrusage diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index a3f5921bd..a8ef1c6a1 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -110,7 +110,7 @@ XX(ERROR_NO_SOURCE, 1097, "NoSource", "No source found") \ XX(ERROR_STREAM_DISPOSING, 1098, "StreamDisposing", "Stream is disposing") \ XX(ERROR_NOT_IMPLEMENTED, 1099, "NotImplemented", "Feature is not implemented") \ - XX(ERROR_NOT_SUPPORTED, 1100, "NotSupported", "Feature is not supported") \ + XX(ERROR_NOT_SUPPORTED, 1100, "NotSupported", "Feature is not supported") \ XX(ERROR_SYSTEM_FILE_UNLINK, 1101, "FileUnlink", "Failed to unlink file") /**************************************************/ diff --git a/trunk/src/kernel/srs_kernel_hourglass.cpp b/trunk/src/kernel/srs_kernel_hourglass.cpp index 0ed1d8535..fc007073c 100644 --- a/trunk/src/kernel/srs_kernel_hourglass.cpp +++ b/trunk/src/kernel/srs_kernel_hourglass.cpp @@ -30,6 +30,14 @@ extern SrsPps *_srs_pps_clock_80ms; extern SrsPps *_srs_pps_clock_160ms; extern SrsPps *_srs_pps_timer_s; +ISrsHourGlassHandler::ISrsHourGlassHandler() +{ +} + +ISrsHourGlassHandler::~ISrsHourGlassHandler() +{ +} + ISrsHourGlass::ISrsHourGlass() { } @@ -38,7 +46,7 @@ ISrsHourGlass::~ISrsHourGlass() { } -SrsHourGlass::SrsHourGlass(string label, ISrsHourGlass *h, srs_utime_t resolution) +SrsHourGlass::SrsHourGlass(string label, ISrsHourGlassHandler *h, srs_utime_t resolution) { label_ = label; handler_ = h; diff --git a/trunk/src/kernel/srs_kernel_hourglass.hpp b/trunk/src/kernel/srs_kernel_hourglass.hpp index 4d67c5c5a..8fbb5556a 100644 --- a/trunk/src/kernel/srs_kernel_hourglass.hpp +++ b/trunk/src/kernel/srs_kernel_hourglass.hpp @@ -18,6 +18,18 @@ class ISrsCoroutine; // The handler for the tick. +class ISrsHourGlassHandler +{ +public: + ISrsHourGlassHandler(); + virtual ~ISrsHourGlassHandler(); + +public: + // When time is ticked, this function is called. + virtual srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick) = 0; +}; + +// The interface for hourglass. class ISrsHourGlass { public: @@ -25,8 +37,11 @@ public: virtual ~ISrsHourGlass(); public: - // When time is ticked, this function is called. - virtual srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick) = 0; + virtual srs_error_t start() = 0; + virtual void stop() = 0; + virtual srs_error_t tick(srs_utime_t interval) = 0; + virtual srs_error_t tick(int event, srs_utime_t interval) = 0; + virtual void untick(int event) = 0; }; // The hourglass(timer or SrsTimer) for special tasks, @@ -53,12 +68,12 @@ public: // // // The hg will create a thread for timer. // hg->start(); -class SrsHourGlass : public ISrsCoroutineHandler +class SrsHourGlass : public ISrsCoroutineHandler, public ISrsHourGlass { private: std::string label_; ISrsCoroutine *trd_; - ISrsHourGlass *handler_; + ISrsHourGlassHandler *handler_; srs_utime_t resolution_; ISrsTime *time_; // The ticks: @@ -71,7 +86,7 @@ private: public: // TODO: FIMXE: Refine to SrsHourGlass(std::string label); - SrsHourGlass(std::string label, ISrsHourGlass *h, srs_utime_t resolution); + SrsHourGlass(std::string label, ISrsHourGlassHandler *h, srs_utime_t resolution); virtual ~SrsHourGlass(); public: diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 2bf5d1206..aa20d7a30 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -401,7 +401,8 @@ int srs_do_create_dir_recursively(string dir) int ret = ERROR_SUCCESS; // stat current dir, if exists, return error. - if (srs_path_exists(dir)) { + SrsPath path; + if (path.exists(dir)) { return ERROR_SYSTEM_DIR_EXISTS; } @@ -516,17 +517,6 @@ bool srs_bytes_equal(void *pa, void *pb, int size) return true; } -srs_error_t srs_os_mkdir_all(string dir) -{ - int ret = srs_do_create_dir_recursively(dir); - - if (ret == ERROR_SYSTEM_DIR_EXISTS || ret == ERROR_SUCCESS) { - return srs_success; - } - - return srs_error_new(ret, "create dir %s", dir.c_str()); -} - SrsPath::SrsPath() { } @@ -536,20 +526,6 @@ SrsPath::~SrsPath() } bool SrsPath::exists(std::string path) -{ - return srs_path_exists(path); -} - -srs_error_t SrsPath::unlink(std::string path) -{ - if (::unlink(path.c_str()) < 0) { - return srs_error_new(ERROR_SYSTEM_FILE_UNLINK, "unlink %s", path.c_str()); - } - - return srs_success; -} - -bool srs_path_exists(std::string path) { struct stat st; @@ -561,7 +537,16 @@ bool srs_path_exists(std::string path) return false; } -string srs_path_filepath_dir(string path) +srs_error_t SrsPath::unlink(std::string path) +{ + if (::unlink(path.c_str()) < 0) { + return srs_error_new(ERROR_SYSTEM_FILE_UNLINK, "unlink %s", path.c_str()); + } + + return srs_success; +} + +std::string SrsPath::filepath_dir(std::string path) { std::string dirname = path; @@ -581,7 +566,7 @@ string srs_path_filepath_dir(string path) return dirname; } -string srs_path_filepath_base(string path) +string SrsPath::filepath_base(string path) { std::string dirname = path; size_t pos = string::npos; @@ -597,7 +582,7 @@ string srs_path_filepath_base(string path) return dirname; } -string srs_path_filepath_filename(string path) +string SrsPath::filepath_filename(string path) { std::string filename = path; size_t pos = string::npos; @@ -609,7 +594,7 @@ string srs_path_filepath_filename(string path) return filename; } -string srs_path_filepath_ext(string path) +string SrsPath::filepath_ext(string path) { size_t pos = string::npos; @@ -620,6 +605,17 @@ string srs_path_filepath_ext(string path) return ""; } +srs_error_t SrsPath::mkdir_all(string dir) +{ + int ret = srs_do_create_dir_recursively(dir); + + if (ret == ERROR_SYSTEM_DIR_EXISTS || ret == ERROR_SUCCESS) { + return srs_success; + } + + return srs_error_new(ret, "create dir %s", dir.c_str()); +} + // fromHexChar converts a hex character into its value and a success flag. uint8_t srs_from_hex_char(uint8_t c) { @@ -691,28 +687,36 @@ int srs_hex_decode_string(uint8_t *data, const char *p, int size) return size / 2; } -void srs_rand_gen_bytes(char *bytes, int size) +SrsRand::SrsRand() +{ +} + +SrsRand::~SrsRand() +{ +} + +void SrsRand::gen_bytes(char *bytes, int size) { for (int i = 0; i < size; i++) { // the common value in [0x0f, 0xf0] - bytes[i] = 0x0f + (srs_rand_integer() % (256 - 0x0f - 0x0f)); + bytes[i] = 0x0f + (integer() % (256 - 0x0f - 0x0f)); } } -std::string srs_rand_gen_str(int len) +std::string SrsRand::gen_str(int len) { static string random_table = "01234567890123456789012345678901234567890123456789abcdefghijklmnopqrstuvwxyz"; string ret; ret.reserve(len); for (int i = 0; i < len; ++i) { - ret.append(1, random_table[srs_rand_integer() % random_table.size()]); + ret.append(1, random_table[integer() % random_table.size()]); } return ret; } -long srs_rand_integer() +long SrsRand::integer() { static bool _random_initialized = false; if (!_random_initialized) { @@ -723,9 +727,9 @@ long srs_rand_integer() return random(); } -long srs_rand_integer(long min, long max) +long SrsRand::integer(long min, long max) { - return min + (srs_rand_integer() % (max - min + 1)); + return min + (integer() % (max - min + 1)); } bool srs_is_digit_number(string str) diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp index 39f843bab..77aec94f1 100644 --- a/trunk/src/kernel/srs_kernel_utility.hpp +++ b/trunk/src/kernel/srs_kernel_utility.hpp @@ -126,9 +126,6 @@ bool srs_strings_equal(std::vector &a, std::vector &b) // @return true if completely equal; otherwise, false. extern bool srs_bytes_equal(void *pa, void *pb, int size); -// Create dir recursively -extern srs_error_t srs_os_mkdir_all(std::string dir); - // The path utility. class SrsPath { @@ -141,18 +138,19 @@ public: virtual bool exists(std::string path); // Remove or unlink file. virtual srs_error_t unlink(std::string path); -}; + // Get the dirname of path, for instance, dirname("/live/livestream")="/live" + virtual std::string filepath_dir(std::string path); + // Get the basename of path, for instance, basename("/live/livestream")="livestream" + virtual std::string filepath_base(std::string path); + // Get the filename of path, for instance, filename("livestream.flv")="livestream" + virtual std::string filepath_filename(std::string path); + // Get the file extension of path, for instance, filext("live.flv")=".flv" + virtual std::string filepath_ext(std::string path); -// Whether path exists. -extern bool srs_path_exists(std::string path); -// Get the dirname of path, for instance, dirname("/live/livestream")="/live" -extern std::string srs_path_filepath_dir(std::string path); -// Get the basename of path, for instance, basename("/live/livestream")="livestream" -extern std::string srs_path_filepath_base(std::string path); -// Get the filename of path, for instance, filename("livestream.flv")="livestream" -extern std::string srs_path_filepath_filename(std::string path); -// Get the file extension of path, for instance, filext("live.flv")=".flv" -extern std::string srs_path_filepath_ext(std::string path); +public: + // Create dir recursively + virtual srs_error_t mkdir_all(std::string dir); +}; // Covert hex string p to uint8 data, for example: // srs_hex_decode_string(data, string("139056E5A0")) @@ -166,15 +164,22 @@ extern char *srs_hex_encode_to_string(char *des, const uint8_t *src, int len); // Output in lowercase, such as string("f33f"). extern char *srs_hex_encode_to_string_lowercase(char *des, const uint8_t *src, int len); -// Generate ramdom data for handshake. -extern void srs_rand_gen_bytes(char *bytes, int size); +// The random generator. +class SrsRand +{ +public: + SrsRand(); + virtual ~SrsRand(); -// Generate random string [0-9a-z] in size of len bytes. -extern std::string srs_rand_gen_str(int len); - -// Generate random value, use srandom(now_us) to init seed if not initialized. -extern long srs_rand_integer(); -extern long srs_rand_integer(long min, long max); +public: + // Generate ramdom data for handshake. + virtual void gen_bytes(char *bytes, int size); + // Generate random string [0-9a-z] in size of len bytes. + virtual std::string gen_str(int len); + // Generate random value, use srandom(now_us) to init seed if not initialized. + virtual long integer(); + virtual long integer(long min, long max); +}; // Whether string is digit number // is_digit("0") is true diff --git a/trunk/src/protocol/srs_protocol_http_conn.cpp b/trunk/src/protocol/srs_protocol_http_conn.cpp index 902cfa97d..017d1f1fa 100644 --- a/trunk/src/protocol/srs_protocol_http_conn.cpp +++ b/trunk/src/protocol/srs_protocol_http_conn.cpp @@ -388,7 +388,8 @@ srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) // If no host in header, we use local discovered IP, IPv4 first. if (host.empty()) { - host = srs_get_public_internet_address(true); + SrsProtocolUtility utility; + host = utility.public_internet_address(true); } // The url must starts with slash if no schema. For example, SIP request line starts with "sip". @@ -406,7 +407,8 @@ srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) } // parse ext. - _ext = srs_path_filepath_ext(_uri->get_path()); + SrsPath path; + _ext = path.filepath_ext(_uri->get_path()); // parse query string. srs_net_url_parse_query(_uri->get_query(), _query); @@ -633,7 +635,8 @@ ISrsRequest *SrsHttpMessage::to_request(string vhost) req->app_ = srs_strings_trim_start(req->app_, "/"); // remove the extension, for instance, livestream.flv to livestream - req->stream_ = srs_path_filepath_filename(req->stream_); + SrsPath path; + req->stream_ = path.filepath_filename(req->stream_); // generate others. req->tcUrl_ = "rtmp://" + vhost + "/" + req->app_; @@ -703,12 +706,14 @@ SrsHttpMessageWriter::SrsHttpMessageWriter(ISrsProtocolReadWriter *io, ISrsHttpF iovss_cache_ = NULL; hf_ = NULL; flw_ = flw; + protocol_utility_ = new SrsProtocolUtility(); } SrsHttpMessageWriter::~SrsHttpMessageWriter() { srs_freep(hdr_); srs_freepa(iovss_cache_); + srs_freep(protocol_utility_); } srs_error_t SrsHttpMessageWriter::final_request() @@ -874,7 +879,7 @@ srs_error_t SrsHttpMessageWriter::writev(const iovec *iov, int iovcnt, ssize_t * // sendout all ioves. ssize_t nwrite = 0; - if ((err = srs_write_large_iovs(skt_, iovss, nb_iovss, &nwrite)) != srs_success) { + if ((err = protocol_utility_->write_iovs(skt_, iovss, nb_iovss, &nwrite)) != srs_success) { return srs_error_wrap(err, "writev large iovs"); } diff --git a/trunk/src/protocol/srs_protocol_http_conn.hpp b/trunk/src/protocol/srs_protocol_http_conn.hpp index 69ac6ae2f..18ef86745 100644 --- a/trunk/src/protocol/srs_protocol_http_conn.hpp +++ b/trunk/src/protocol/srs_protocol_http_conn.hpp @@ -20,6 +20,7 @@ class ISrsRequest; class ISrsReader; class SrsHttpResponseReader; class ISrsProtocolReadWriter; +class SrsProtocolUtility; // A wrapper for llhttp, // provides HTTP message originted service. @@ -261,6 +262,8 @@ private: // (*response).wroteHeader, which tells only whether it was // logically written. bool header_sent_; + // Protocol utility for writing large iovs. + SrsProtocolUtility *protocol_utility_; public: SrsHttpMessageWriter(ISrsProtocolReadWriter *io, ISrsHttpFirstLineWriter *flw); diff --git a/trunk/src/protocol/srs_protocol_http_stack.cpp b/trunk/src/protocol/srs_protocol_http_stack.cpp index 00bb132c8..4bc180c18 100644 --- a/trunk/src/protocol/srs_protocol_http_stack.cpp +++ b/trunk/src/protocol/srs_protocol_http_stack.cpp @@ -532,12 +532,13 @@ SrsHttpFileServer::SrsHttpFileServer(string root_dir) { dir = root_dir; fs_factory = new ISrsFileReaderFactory(); - _srs_path_exists = srs_path_exists; + path_ = new SrsPath(); } SrsHttpFileServer::~SrsHttpFileServer() { srs_freep(fs_factory); + srs_freep(path_); } void SrsHttpFileServer::set_fs_factory(ISrsFileReaderFactory *f) @@ -546,9 +547,10 @@ void SrsHttpFileServer::set_fs_factory(ISrsFileReaderFactory *f) fs_factory = f; } -void SrsHttpFileServer::set_path_check(_pfn_srs_path_exists pfn) +void SrsHttpFileServer::set_path(SrsPath *v) { - _srs_path_exists = pfn; + srs_freep(path_); + path_ = v; } srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) @@ -561,10 +563,11 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMes string upath = r->path(); string fullpath = srs_http_fs_fullpath(dir, entry->pattern, upath); - string basename = srs_path_filepath_base(upath); + SrsPath path; + string basename = path.filepath_base(upath); // stat current dir, if exists, return error. - if (!_srs_path_exists(fullpath)) { + if (!path_->exists(fullpath)) { srs_warn("http miss file=%s, pattern=%s, upath=%s", fullpath.c_str(), entry->pattern.c_str(), upath.c_str()); return SrsHttpNotFoundHandler().serve_http(w, r); @@ -640,7 +643,8 @@ srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter *w, ISrsHttpMes } if (true) { - std::string ext = srs_path_filepath_ext(fullpath); + SrsPath path; + std::string ext = path.filepath_ext(fullpath); if (_mime.find(ext) == _mime.end()) { w->header()->set_content_type("application/octet-stream"); diff --git a/trunk/src/protocol/srs_protocol_http_stack.hpp b/trunk/src/protocol/srs_protocol_http_stack.hpp index 7765cd519..234bd59f4 100644 --- a/trunk/src/protocol/srs_protocol_http_stack.hpp +++ b/trunk/src/protocol/srs_protocol_http_stack.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -327,9 +328,6 @@ public: virtual srs_error_t serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r); }; -// For utest to mock it. -typedef bool (*_pfn_srs_path_exists)(std::string path); - // Build the file path from request r. extern std::string srs_http_fs_fullpath(std::string dir, std::string pattern, std::string upath); @@ -348,7 +346,7 @@ protected: protected: ISrsFileReaderFactory *fs_factory; - _pfn_srs_path_exists _srs_path_exists; + SrsPath *path_; public: SrsHttpFileServer(std::string root_dir); @@ -357,8 +355,8 @@ public: private: // For utest to mock the fs. virtual void set_fs_factory(ISrsFileReaderFactory *v); - // For utest to mock the path check function. - virtual void set_path_check(_pfn_srs_path_exists pfn); + // For utest to mock the path utility. + virtual void set_path(SrsPath *v); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r); diff --git a/trunk/src/protocol/srs_protocol_log.cpp b/trunk/src/protocol/srs_protocol_log.cpp index c2d20f586..2f96d4e36 100644 --- a/trunk/src/protocol/srs_protocol_log.cpp +++ b/trunk/src/protocol/srs_protocol_log.cpp @@ -33,8 +33,9 @@ SrsThreadContext::~SrsThreadContext() SrsContextId SrsThreadContext::generate_id() { + SrsRand rand; SrsContextId cid; - return cid.set_value(srs_rand_gen_str(8)); + return cid.set_value(rand.gen_str(8)); } static SrsContextId _srs_context_default; diff --git a/trunk/src/protocol/srs_protocol_rtmp_conn.cpp b/trunk/src/protocol/srs_protocol_rtmp_conn.cpp index 78093866f..39ed92fb5 100644 --- a/trunk/src/protocol/srs_protocol_rtmp_conn.cpp +++ b/trunk/src/protocol/srs_protocol_rtmp_conn.cpp @@ -89,7 +89,8 @@ void SrsBasicRtmpClient::close() srs_error_t SrsBasicRtmpClient::connect_app() { - return do_connect_app(srs_get_public_internet_address(), false); + SrsProtocolUtility utility; + return do_connect_app(utility.public_internet_address(), false); } srs_error_t SrsBasicRtmpClient::do_connect_app(string local_ip, bool debug) diff --git a/trunk/src/protocol/srs_protocol_rtmp_handshake.cpp b/trunk/src/protocol/srs_protocol_rtmp_handshake.cpp index 6a7db97e8..cb71a5752 100644 --- a/trunk/src/protocol/srs_protocol_rtmp_handshake.cpp +++ b/trunk/src/protocol/srs_protocol_rtmp_handshake.cpp @@ -333,7 +333,8 @@ srs_error_t SrsDH::do_initialize() SrsKeyBlock::SrsKeyBlock() { - offset_ = (int32_t)srs_rand_integer(); + SrsRand rand; + offset_ = (int32_t)rand.integer(); random0_ = NULL; random1_ = NULL; @@ -343,16 +344,16 @@ SrsKeyBlock::SrsKeyBlock() random0_size_ = valid_offset; if (random0_size_ > 0) { random0_ = new char[random0_size_]; - srs_rand_gen_bytes(random0_, random0_size_); + rand_.gen_bytes(random0_, random0_size_); snprintf(random0_, random0_size_, "%s", RTMP_SIG_SRS_HANDSHAKE); } - srs_rand_gen_bytes(key_, sizeof(key_)); + rand_.gen_bytes(key_, sizeof(key_)); random1_size_ = 764 - valid_offset - 128 - 4; if (random1_size_ > 0) { random1_ = new char[random1_size_]; - srs_rand_gen_bytes(random1_, random1_size_); + rand_.gen_bytes(random1_, random1_size_); snprintf(random1_, random1_size_, "%s", RTMP_SIG_SRS_HANDSHAKE); } } @@ -415,7 +416,8 @@ int SrsKeyBlock::calc_valid_offset() SrsDigestBlock::SrsDigestBlock() { - offset_ = (int32_t)srs_rand_integer(); + SrsRand rand; + offset_ = (int32_t)rand.integer(); random0_ = NULL; random1_ = NULL; @@ -425,16 +427,16 @@ SrsDigestBlock::SrsDigestBlock() random0_size_ = valid_offset; if (random0_size_ > 0) { random0_ = new char[random0_size_]; - srs_rand_gen_bytes(random0_, random0_size_); + rand_.gen_bytes(random0_, random0_size_); snprintf(random0_, random0_size_, "%s", RTMP_SIG_SRS_HANDSHAKE); } - srs_rand_gen_bytes(digest_, sizeof(digest_)); + rand_.gen_bytes(digest_, sizeof(digest_)); random1_size_ = 764 - 4 - valid_offset - 32; if (random1_size_ > 0) { random1_ = new char[random1_size_]; - srs_rand_gen_bytes(random1_, random1_size_); + rand_.gen_bytes(random1_, random1_size_); snprintf(random1_, random1_size_, "%s", RTMP_SIG_SRS_HANDSHAKE); } } @@ -945,13 +947,13 @@ srs_error_t SrsC1S1::s1_validate_digest(bool &is_valid) SrsC2S2::SrsC2S2() { - srs_rand_gen_bytes(random_, 1504); + rand_.gen_bytes(random_, 1504); int size = snprintf(random_, 1504, "%s", RTMP_SIG_SRS_HANDSHAKE); srs_assert(size > 0 && size < 1504); snprintf(random_ + 1504 - size, size, "%s", RTMP_SIG_SRS_HANDSHAKE); - srs_rand_gen_bytes(digest_, 32); + rand_.gen_bytes(digest_, 32); } SrsC2S2::~SrsC2S2() diff --git a/trunk/src/protocol/srs_protocol_rtmp_handshake.hpp b/trunk/src/protocol/srs_protocol_rtmp_handshake.hpp index 27c1d9af4..aa90c1103 100644 --- a/trunk/src/protocol/srs_protocol_rtmp_handshake.hpp +++ b/trunk/src/protocol/srs_protocol_rtmp_handshake.hpp @@ -8,6 +8,7 @@ #define SRS_PROTOCOL_HANDSHAKE_HPP #include +#include class ISrsProtocolReadWriter; class SrsComplexHandshake; @@ -90,6 +91,9 @@ enum srs_schema_type { // @see also: http://blog.csdn.net/win_lin/article/details/13006803 class SrsKeyBlock { +private: + SrsRand rand_; + public: // (offset)bytes char *random0_; @@ -129,6 +133,9 @@ private: // @see also: http://blog.csdn.net/win_lin/article/details/13006803 class SrsDigestBlock { +private: + SrsRand rand_; + public: // 4bytes int32_t offset_; @@ -382,6 +389,9 @@ public: // @see also: http://blog.csdn.net/win_lin/article/details/13006803 class SrsC2S2 { +private: + SrsRand rand_; + public: char random_[1504]; char digest_[32]; diff --git a/trunk/src/protocol/srs_protocol_rtmp_stack.cpp b/trunk/src/protocol/srs_protocol_rtmp_stack.cpp index 4fd46609e..3e5e7adcc 100644 --- a/trunk/src/protocol/srs_protocol_rtmp_stack.cpp +++ b/trunk/src/protocol/srs_protocol_rtmp_stack.cpp @@ -184,6 +184,8 @@ SrsProtocol::SrsProtocol(ISrsProtocolReadWriter *io) show_debug_info_ = true; in_buffer_length_ = 0; + protocol_utility_ = new SrsProtocolUtility(); + cs_cache_ = NULL; if (SRS_PERF_CHUNK_STREAM_CACHE > 0) { cs_cache_ = new SrsChunkStream *[SRS_PERF_CHUNK_STREAM_CACHE]; @@ -234,6 +236,8 @@ SrsProtocol::~SrsProtocol() srs_freepa(cs_cache_); srs_freepa(out_c0c3_caches_); + + srs_freep(protocol_utility_); } void SrsProtocol::set_auto_response(bool v) @@ -539,7 +543,7 @@ srs_error_t SrsProtocol::do_send_messages(SrsMediaPacket **msgs, int nb_msgs) srs_error_t SrsProtocol::do_iovs_send(iovec *iovs, int size) { - return srs_write_large_iovs(skt_, iovs, size); + return protocol_utility_->write_iovs(skt_, iovs, size); } srs_error_t SrsProtocol::do_send_and_free_packet(SrsRtmpCommand *packet_raw, int stream_id) @@ -1706,7 +1710,7 @@ srs_error_t SrsHandshakeBytes::create_c0c1() } c0c1_ = new char[1537]; - srs_rand_gen_bytes(c0c1_, 1537); + rand_.gen_bytes(c0c1_, 1537); // plain text required. SrsBuffer stream(c0c1_, 9); @@ -1727,7 +1731,7 @@ srs_error_t SrsHandshakeBytes::create_s0s1s2(const char *c1) } s0s1s2_ = new char[3073]; - srs_rand_gen_bytes(s0s1s2_, 3073); + rand_.gen_bytes(s0s1s2_, 3073); // plain text required. SrsBuffer stream(s0s1s2_, 9); @@ -1757,7 +1761,7 @@ srs_error_t SrsHandshakeBytes::create_c2() } c2_ = new char[1536]; - srs_rand_gen_bytes(c2_, 1536); + rand_.gen_bytes(c2_, 1536); // time SrsBuffer stream(c2_, 8); @@ -2387,7 +2391,8 @@ srs_error_t SrsRtmpServer::redirect(ISrsRequest *r, string url, bool &accepted) // The redirect is tcUrl while redirect2 is RTMP URL. // https://github.com/ossrs/srs/issues/1575#issuecomment-574999798 - string tcUrl = srs_path_filepath_dir(url); + SrsPath path; + string tcUrl = path.filepath_dir(url); ex->set("redirect", SrsAmf0Any::str(tcUrl.c_str())); ex->set("redirect2", SrsAmf0Any::str(url.c_str())); diff --git a/trunk/src/protocol/srs_protocol_rtmp_stack.hpp b/trunk/src/protocol/srs_protocol_rtmp_stack.hpp index af30e21b0..4c8142f8e 100644 --- a/trunk/src/protocol/srs_protocol_rtmp_stack.hpp +++ b/trunk/src/protocol/srs_protocol_rtmp_stack.hpp @@ -22,6 +22,7 @@ #include #include #include +#include class SrsFastStream; class SrsBuffer; @@ -33,6 +34,7 @@ class SrsMediaPacket; class SrsProtocol; class ISrsProtocolReader; class ISrsProtocolReadWriter; +class SrsProtocolUtility; class SrsCreateStreamPacket; class SrsFMLEStartPacket; class SrsPublishPacket; @@ -197,6 +199,8 @@ private: bool warned_c0c3_cache_dry_; // The output chunk size, default to 128, set by config. int32_t out_chunk_size_; + // Protocol utility for writing large iovs. + SrsProtocolUtility *protocol_utility_; public: SrsProtocol(ISrsProtocolReadWriter *io); @@ -539,6 +543,9 @@ bool srs_client_type_is_publish(SrsRtmpConnType type); // For smart switch between complex and simple handshake. class SrsHandshakeBytes { +private: + SrsRand rand_; + public: // For RTMP proxy, the real IP. uint32_t proxy_real_ip_; diff --git a/trunk/src/protocol/srs_protocol_rtsp_stack.cpp b/trunk/src/protocol/srs_protocol_rtsp_stack.cpp index 62f8b18ec..19d743c1e 100644 --- a/trunk/src/protocol/srs_protocol_rtsp_stack.cpp +++ b/trunk/src/protocol/srs_protocol_rtsp_stack.cpp @@ -551,8 +551,9 @@ srs_error_t SrsRtspStack::do_recv_message(SrsRtspRequest *req) // for setup, parse the stream id from uri. if (req->is_setup()) { + SrsPath path; size_t pos = string::npos; - std::string stream_id = srs_path_filepath_base(req->uri_); + std::string stream_id = path.filepath_base(req->uri_); if ((pos = stream_id.find("=")) != string::npos) { stream_id = stream_id.substr(pos + 1); } diff --git a/trunk/src/protocol/srs_protocol_utility.cpp b/trunk/src/protocol/srs_protocol_utility.cpp index bc67ad149..57afbb632 100644 --- a/trunk/src/protocol/srs_protocol_utility.cpp +++ b/trunk/src/protocol/srs_protocol_utility.cpp @@ -79,12 +79,13 @@ void srs_net_url_parse_tcurl(string tcUrl, string &schema, string &host, string schema = uri.get_schema(); host = uri.get_host(); port = uri.get_port(); - stream = srs_path_filepath_base(uri.get_path()); + SrsPath path; + stream = path.filepath_base(uri.get_path()); param = uri.get_query().empty() ? "" : "?" + uri.get_query(); param += uri.get_fragment().empty() ? "" : "#" + uri.get_fragment(); // Parse app without the prefix slash. - app = srs_path_filepath_dir(uri.get_path()); + app = path.filepath_dir(uri.get_path()); if (!app.empty() && app.at(0) == '/') app = app.substr(1); if (app.empty()) @@ -260,7 +261,8 @@ string srs_net_url_encode_sid(string vhost, string app, string stream) } url += "/" + app; // Note that we ignore any extension. - url += "/" + srs_path_filepath_filename(stream); + SrsPath path; + url += "/" + path.filepath_filename(stream); return url; } @@ -341,7 +343,15 @@ srs_error_t srs_rtmp_create_msg(char type, uint32_t timestamp, char *data, int s return err; } -srs_error_t srs_write_large_iovs(ISrsProtocolReadWriter *skt, iovec *iovs, int size, ssize_t *pnwrite) +SrsProtocolUtility::SrsProtocolUtility() +{ +} + +SrsProtocolUtility::~SrsProtocolUtility() +{ +} + +srs_error_t SrsProtocolUtility::write_iovs(ISrsProtocolReadWriter *skt, iovec *iovs, int size, ssize_t *pnwrite) { srs_error_t err = srs_success; @@ -379,7 +389,7 @@ srs_error_t srs_write_large_iovs(ISrsProtocolReadWriter *skt, iovec *iovs, int s // value is whether internet, for instance, true. static std::map _srs_device_ifs; -bool srs_net_device_is_internet(string ifname) +bool SrsProtocolUtility::is_internet(string ifname) { srs_info("check ifname=%s", ifname.c_str()); @@ -389,7 +399,7 @@ bool srs_net_device_is_internet(string ifname) return _srs_device_ifs[ifname]; } -bool srs_net_device_is_internet(const sockaddr *addr) +bool SrsProtocolUtility::is_internet(const sockaddr *addr) { if (addr->sa_family == AF_INET) { const in_addr inaddr = ((sockaddr_in *)addr)->sin_addr; @@ -465,9 +475,7 @@ bool srs_net_device_is_internet(const sockaddr *addr) return true; } -vector _srs_system_ips; - -void discover_network_iface(ifaddrs *cur, vector &ips, stringstream &ss0, stringstream &ss1, bool ipv6, bool loopback) +void discover_network_iface(SrsProtocolUtility *utility, ifaddrs *cur, vector &ips, stringstream &ss0, stringstream &ss1, bool ipv6, bool loopback) { char saddr[64]; char *h = (char *)saddr; @@ -487,7 +495,7 @@ void discover_network_iface(ifaddrs *cur, vector &ips, stringstr ip_address->is_ipv4_ = !ipv6; ip_address->is_loopback_ = loopback; ip_address->ifname_ = cur->ifa_name; - ip_address->is_internet_ = srs_net_device_is_internet(cur->ifa_addr); + ip_address->is_internet_ = utility->is_internet(cur->ifa_addr); ips.push_back(ip_address); // set the device internet status. @@ -501,7 +509,9 @@ void discover_network_iface(ifaddrs *cur, vector &ips, stringstr ss1 << cur->ifa_name << " " << ip; } -void retrieve_local_ips() +vector _srs_system_ips; + +void retrieve_local_ips(SrsProtocolUtility *utility) { vector &ips = _srs_system_ips; @@ -536,7 +546,7 @@ void retrieve_local_ips() bool ignored = (!cur->ifa_addr) || (cur->ifa_flags & IFF_LOOPBACK) || (cur->ifa_flags & IFF_POINTOPOINT); bool loopback = (cur->ifa_flags & IFF_LOOPBACK); if (ipv4 && ready && !ignored) { - discover_network_iface(cur, ips, ss0, ss1, false, loopback); + discover_network_iface(utility, cur, ips, ss0, ss1, false, loopback); } } @@ -557,7 +567,7 @@ void retrieve_local_ips() bool ignored = (!cur->ifa_addr) || (cur->ifa_flags & IFF_POINTOPOINT) || (cur->ifa_flags & IFF_PROMISC) || (cur->ifa_flags & IFF_LOOPBACK); bool loopback = (cur->ifa_flags & IFF_LOOPBACK); if (ipv6 && ready && !ignored) { - discover_network_iface(cur, ips, ss0, ss1, true, loopback); + discover_network_iface(utility, cur, ips, ss0, ss1, true, loopback); } } @@ -579,7 +589,7 @@ void retrieve_local_ips() bool ignored = (!cur->ifa_addr) || (cur->ifa_flags & IFF_POINTOPOINT) || (cur->ifa_flags & IFF_PROMISC); bool loopback = (cur->ifa_flags & IFF_LOOPBACK); if (ipv4 && ready && !ignored) { - discover_network_iface(cur, ips, ss0, ss1, false, loopback); + discover_network_iface(utility, cur, ips, ss0, ss1, false, loopback); } } } @@ -590,10 +600,10 @@ void retrieve_local_ips() freeifaddrs(ifap); } -vector &srs_get_local_ips() +vector &SrsProtocolUtility::local_ips() { if (_srs_system_ips.empty()) { - retrieve_local_ips(); + retrieve_local_ips(this); } return _srs_system_ips; @@ -601,13 +611,13 @@ vector &srs_get_local_ips() std::string _public_internet_address; -string srs_get_public_internet_address(bool ipv4_only) +string SrsProtocolUtility::public_internet_address(bool ipv4_only) { if (!_public_internet_address.empty()) { return _public_internet_address; } - std::vector &ips = srs_get_local_ips(); + std::vector &ips = local_ips(); // find the best match public address. for (int i = 0; i < (int)ips.size(); i++) { @@ -678,7 +688,7 @@ string srs_get_original_ip(ISrsHttpMessage *r) std::string _srs_system_hostname; -string srs_get_system_hostname() +string SrsProtocolUtility::system_hostname() { if (!_srs_system_hostname.empty()) { return _srs_system_hostname; @@ -695,7 +705,7 @@ string srs_get_system_hostname() } #if defined(__linux__) || defined(SRS_OSX) -utsname *srs_get_system_uname_info() +utsname *SrsProtocolUtility::system_uname() { static utsname *system_info = NULL; @@ -838,7 +848,8 @@ bool srs_srt_streamid_to_request(const std::string &streamid, SrtMode &mode, ISr request->param_ = stream_with_params.substr(pos + 1); } - request->host_ = srs_get_public_internet_address(); + SrsProtocolUtility utility; + request->host_ = utility.public_internet_address(); if (request->vhost_.empty()) request->vhost_ = request->host_; request->tcUrl_ = srs_net_url_encode_tcurl("srt", request->host_, request->vhost_, request->app_, request->port_); diff --git a/trunk/src/protocol/srs_protocol_utility.hpp b/trunk/src/protocol/srs_protocol_utility.hpp index ef01756a5..889ee72db 100644 --- a/trunk/src/protocol/srs_protocol_utility.hpp +++ b/trunk/src/protocol/srs_protocol_utility.hpp @@ -29,6 +29,8 @@ #include #endif +#include + class ISrsHttpMessage; class SrsMessageHeader; @@ -103,10 +105,6 @@ extern std::string srs_net_url_encode_rtmp_url(std::string server, int port, std */ extern srs_error_t srs_rtmp_create_msg(char type, uint32_t timestamp, char *data, int size, int stream_id, SrsRtmpCommonMessage **ppmsg); -// write large numbers of iovs. -extern srs_error_t srs_write_large_iovs(ISrsProtocolReadWriter *skt, iovec *iovs, int size, ssize_t *pnwrite = NULL); - -// Get local ip, fill to @param ips struct SrsIPAddress { // The network interface name, such as eth0, en0, eth1. std::string ifname_; @@ -119,26 +117,39 @@ struct SrsIPAddress { // Whether the ip is loopback, such as 127.0.0.1 bool is_loopback_; }; -extern std::vector &srs_get_local_ips(); -// Get local public ip, empty string if no public internet address found. -extern std::string srs_get_public_internet_address(bool ipv4_only = false); +// The utility functions for protocol. +class SrsProtocolUtility +{ +public: + SrsProtocolUtility(); + virtual ~SrsProtocolUtility(); -// Detect whether specified device is internet public address. -extern bool srs_net_device_is_internet(std::string ifname); -extern bool srs_net_device_is_internet(const sockaddr *addr); +public: + // write large numbers of iovs. + virtual srs_error_t write_iovs(ISrsProtocolReadWriter *skt, iovec *iovs, int size, ssize_t *pnwrite = NULL); + +public: + // Get local ip, fill to @param ips + virtual std::vector &local_ips(); + // Get local public ip, empty string if no public internet address found. + virtual std::string public_internet_address(bool ipv4_only = false); + // Detect whether specified device is internet public address. + virtual bool is_internet(std::string ifname); + virtual bool is_internet(const sockaddr *addr); + +public: + // Get hostname + virtual std::string system_hostname(void); +#if defined(__linux__) || defined(SRS_OSX) + // Get system uname info. + virtual utsname *system_uname(); +#endif +}; // Get the original ip from query and header by proxy. extern std::string srs_get_original_ip(ISrsHttpMessage *r); -// Get hostname -extern std::string srs_get_system_hostname(void); - -#if defined(__linux__) || defined(SRS_OSX) -// Get system uname info. -extern utsname *srs_get_system_uname_info(); -#endif - class ISrsRequest; enum SrtMode { diff --git a/trunk/src/utest/srs_utest.cpp b/trunk/src/utest/srs_utest.cpp index 7ee63977a..f047a4bb5 100644 --- a/trunk/src/utest/srs_utest.cpp +++ b/trunk/src/utest/srs_utest.cpp @@ -316,7 +316,8 @@ SrsHttpTestServer::SrsHttpTestServer(string response_body) : response_body_(resp fd_ = NULL; ip_ = "127.0.0.1"; // Generate random port in range [30000, 60000] - port_ = srs_rand_integer(30000, 60000); + SrsRand rand; + port_ = rand.integer(30000, 60000); } SrsHttpTestServer::~SrsHttpTestServer() @@ -339,7 +340,8 @@ srs_error_t SrsHttpTestServer::start() // If this is not the last retry, generate a new random port and try again if (retry < 2) { srs_freep(err); - port_ = srs_rand_integer(30000, 60000); + SrsRand rand; + port_ = rand.integer(30000, 60000); srs_trace("HTTP test server listen failed on %s:%d, retry %d with new port %d", ip_.c_str(), port_, retry + 1, port_); } @@ -421,7 +423,8 @@ SrsHttpsTestServer::SrsHttpsTestServer(string response_body, string key_file, st fd_ = NULL; ip_ = "127.0.0.1"; // Generate random port in range [30000, 60000] - port_ = srs_rand_integer(30000, 60000); + SrsRand rand; + port_ = rand.integer(30000, 60000); } SrsHttpsTestServer::~SrsHttpsTestServer() @@ -443,7 +446,8 @@ srs_error_t SrsHttpsTestServer::start() // If this is not the last retry, generate a new random port and try again if (retry < 2) { srs_freep(err); - port_ = srs_rand_integer(30000, 60000); + SrsRand rand; + port_ = rand.integer(30000, 60000); srs_trace("HTTPS test server listen failed on %s:%d, retry %d with new port %d", ip_.c_str(), port_, retry + 1, port_); } @@ -544,7 +548,8 @@ SrsRtmpTestServer::SrsRtmpTestServer(string app, string stream) : app_(app), str enable_publish_ = true; enable_play_ = true; // Generate random port in range [30000, 60000] - port_ = srs_rand_integer(30000, 60000); + SrsRand rand; + port_ = rand.integer(30000, 60000); } SrsRtmpTestServer::~SrsRtmpTestServer() @@ -567,7 +572,8 @@ srs_error_t SrsRtmpTestServer::start() // If this is not the last retry, generate a new random port and try again if (retry < 2) { srs_freep(err); - port_ = srs_rand_integer(30000, 60000); + SrsRand rand; + port_ = rand.integer(30000, 60000); srs_trace("RTMP test server listen failed on %s:%d, retry %d with new port %d", ip_.c_str(), port_, retry + 1, port_); } @@ -713,7 +719,8 @@ SrsTestTcpServer::SrsTestTcpServer(string ip) conn_ = NULL; ip_ = ip; // Generate random port in range [30000, 60000] - port_ = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); + SrsRand rand; + port_ = 30000 + (rand.integer() % (60000 - 30000 + 1)); } SrsTestTcpServer::~SrsTestTcpServer() @@ -863,7 +870,8 @@ SrsUdpTestServer::SrsUdpTestServer(string host) socket_ = NULL; started_ = false; // Generate random port in range [30000, 60000] - port_ = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); + SrsRand rand; + port_ = 30000 + (rand.integer() % (60000 - 30000 + 1)); } SrsUdpTestServer::~SrsUdpTestServer() @@ -890,7 +898,8 @@ srs_error_t SrsUdpTestServer::start() // If this is not the last retry, generate a new random port and try again if (retry < 2) { srs_freep(err); - port_ = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); + SrsRand rand; + port_ = 30000 + (rand.integer() % (60000 - 30000 + 1)); srs_trace("UDP test server listen failed on %s:%d, retry %d with new port %d", host_.c_str(), port_, retry + 1, port_); } diff --git a/trunk/src/utest/srs_utest_app6.cpp b/trunk/src/utest/srs_utest_app6.cpp index eb28ee789..9d511e4c2 100644 --- a/trunk/src/utest/srs_utest_app6.cpp +++ b/trunk/src/utest/srs_utest_app6.cpp @@ -2170,6 +2170,16 @@ std::string MockAppConfig::get_default_app_name() return "live"; } +void MockAppConfig::subscribe(ISrsReloadHandler *handler) +{ + // Do nothing in mock +} + +void MockAppConfig::unsubscribe(ISrsReloadHandler *handler) +{ + // Do nothing in mock +} + bool MockAppConfig::get_vhost_http_hooks_enabled(std::string vhost) { return http_hooks_enabled_; @@ -2390,6 +2400,66 @@ bool MockAppConfig::get_hls_recover(std::string vhost) return true; } +bool MockAppConfig::get_forward_enabled(std::string vhost) +{ + return false; +} + +SrsConfDirective *MockAppConfig::get_forwards(std::string vhost) +{ + return NULL; +} + +srs_utime_t MockAppConfig::get_queue_length(std::string vhost) +{ + return 30 * SRS_UTIME_SECONDS; +} + +SrsConfDirective *MockAppConfig::get_forward_backend(std::string vhost) +{ + return NULL; +} + +bool MockAppConfig::get_atc(std::string vhost) +{ + return false; +} + +int MockAppConfig::get_time_jitter(std::string vhost) +{ + return SrsRtmpJitterAlgorithmFULL; +} + +bool MockAppConfig::get_mix_correct(std::string vhost) +{ + return false; +} + +bool MockAppConfig::try_annexb_first(std::string vhost) +{ + return true; +} + +bool MockAppConfig::get_vhost_is_edge(std::string vhost) +{ + return false; +} + +bool MockAppConfig::get_atc_auto(std::string vhost) +{ + return false; +} + +bool MockAppConfig::get_reduce_sequence_header(std::string vhost) +{ + return false; +} + +bool MockAppConfig::get_parse_sps(std::string vhost) +{ + return true; +} + void MockAppConfig::set_http_hooks_enabled(bool enabled) { http_hooks_enabled_ = enabled; @@ -2600,6 +2670,26 @@ void MockRtcStatistic::reset() last_client_type_ = SrsRtmpConnUnknown; } +srs_error_t MockRtcStatistic::on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height) +{ + return srs_success; +} + +srs_error_t MockRtcStatistic::on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, SrsAudioChannels asound_type, SrsAacObjectType aac_object) +{ + return srs_success; +} + +void MockRtcStatistic::on_stream_publish(ISrsRequest *req, std::string publisher_id) +{ + // Do nothing in mock +} + +void MockRtcStatistic::on_stream_close(ISrsRequest *req) +{ + // Do nothing in mock +} + // Unit tests for SrsRtcAsyncCallOnStop::call() VOID TEST(RtcAsyncCallOnStopTest, CallWithHttpHooksDisabled) { diff --git a/trunk/src/utest/srs_utest_app6.hpp b/trunk/src/utest/srs_utest_app6.hpp index 2af3e5f2a..7e67d053e 100644 --- a/trunk/src/utest/srs_utest_app6.hpp +++ b/trunk/src/utest/srs_utest_app6.hpp @@ -243,6 +243,53 @@ public: // ISrsConfig methods virtual srs_utime_t get_pithy_print(); virtual std::string get_default_app_name(); + virtual void subscribe(ISrsReloadHandler *handler); + virtual void unsubscribe(ISrsReloadHandler *handler); + virtual srs_error_t reload(SrsReloadState *pstate) { return srs_success; } + virtual srs_error_t persistence() { return srs_success; } + virtual std::string config() { return ""; } + virtual int get_max_connections() { return 1000; } + virtual std::string get_pid_file() { return ""; } + virtual bool empty_ip_ok() { return false; } + virtual bool get_asprocess() { return false; } + virtual srs_utime_t get_grace_start_wait() { return 0; } + virtual srs_utime_t get_grace_final_wait() { return 0; } + virtual bool is_force_grace_quit() { return false; } + virtual bool inotify_auto_reload() { return false; } + virtual bool auto_reload_for_docker() { return false; } + virtual std::vector get_listens() { return std::vector(); } + virtual bool get_rtmps_enabled() { return false; } + virtual std::vector get_rtmps_listen() { return std::vector(); } + virtual bool get_http_api_enabled() { return false; } + virtual std::vector get_http_api_listens() { return std::vector(); } + virtual bool get_https_api_enabled() { return false; } + virtual std::vector get_https_api_listens() { return std::vector(); } + virtual std::string get_https_api_ssl_key() { return ""; } + virtual std::string get_https_api_ssl_cert() { return ""; } + virtual bool get_http_stream_enabled() { return false; } + virtual std::vector get_http_stream_listens() { return std::vector(); } + virtual bool get_https_stream_enabled() { return false; } + virtual std::vector get_https_stream_listens() { return std::vector(); } + virtual std::string get_https_stream_ssl_key() { return ""; } + virtual std::string get_https_stream_ssl_cert() { return ""; } + virtual std::string get_http_stream_dir() { return ""; } + virtual bool get_rtc_server_enabled() { return false; } + virtual bool get_rtc_server_tcp_enabled() { return false; } + virtual std::vector get_rtc_server_tcp_listens() { return std::vector(); } + virtual std::string get_rtc_server_protocol() { return "udp"; } + virtual std::vector get_rtc_server_listens() { return std::vector(); } + virtual int get_rtc_server_reuseport() { return 1; } + virtual bool get_rtsp_server_enabled() { return false; } + virtual std::vector get_rtsp_server_listens() { return std::vector(); } + virtual std::vector get_srt_listens() { return std::vector(); } + virtual std::vector get_stream_casters() { return std::vector(); } + virtual bool get_stream_caster_enabled(SrsConfDirective *conf) { return false; } + virtual std::string get_stream_caster_engine(SrsConfDirective *conf) { return ""; } + virtual bool get_exporter_enabled() { return false; } + virtual std::string get_exporter_listen() { return ""; } + virtual bool get_stats_enabled() { return false; } + virtual bool get_heartbeat_enabled() { return false; } + virtual srs_utime_t get_heartbeat_interval() { return 0; } // ISrsAppConfig methods virtual bool get_vhost_http_hooks_enabled(std::string vhost); virtual SrsConfDirective *get_vhost_on_stop(std::string vhost); @@ -289,6 +336,18 @@ public: virtual bool get_hls_ctx_enabled(std::string vhost); virtual bool get_hls_ts_ctx_enabled(std::string vhost); virtual bool get_hls_recover(std::string vhost); + virtual bool get_forward_enabled(std::string vhost); + virtual SrsConfDirective *get_forwards(std::string vhost); + virtual srs_utime_t get_queue_length(std::string vhost); + virtual SrsConfDirective *get_forward_backend(std::string vhost); + virtual bool get_atc(std::string vhost); + virtual int get_time_jitter(std::string vhost); + virtual bool get_mix_correct(std::string vhost); + virtual bool try_annexb_first(std::string vhost); + virtual bool get_vhost_is_edge(std::string vhost); + virtual bool get_atc_auto(std::string vhost); + virtual bool get_reduce_sequence_header(std::string vhost); + virtual bool get_parse_sps(std::string vhost); void set_http_hooks_enabled(bool enabled); void set_on_stop_urls(const std::vector &urls); void clear_on_stop_directive(); @@ -358,6 +417,10 @@ public: virtual ~MockRtcStatistic(); 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); void set_on_client_error(srs_error_t err); void reset(); }; diff --git a/trunk/src/utest/srs_utest_app8.cpp b/trunk/src/utest/srs_utest_app8.cpp index cf0355d03..779280428 100644 --- a/trunk/src/utest/srs_utest_app8.cpp +++ b/trunk/src/utest/srs_utest_app8.cpp @@ -932,12 +932,12 @@ VOID TEST(HlsMuxerTest, SegmentOverflowAndPureAudio) // Set up required fields MockHlsRequest mock_request("__defaultVhost__", "live", "test"); muxer->req_ = &mock_request; - muxer->hls_fragment_ = 10 * SRS_UTIME_SECONDS; // 10 seconds fragment - muxer->hls_aof_ratio_ = 2.0; // Absolutely overflow at 2x fragment duration + muxer->hls_fragment_ = 10 * SRS_UTIME_SECONDS; // 10 seconds fragment + muxer->hls_aof_ratio_ = 2.0; // Absolutely overflow at 2x fragment duration muxer->hls_wait_keyframe_ = true; - muxer->hls_ts_floor_ = false; // Disable floor for simpler testing + muxer->hls_ts_floor_ = false; // Disable floor for simpler testing muxer->deviation_ts_ = 0; - muxer->max_td_ = 10 * SRS_UTIME_SECONDS; // Same as fragment for simplicity + muxer->max_td_ = 10 * SRS_UTIME_SECONDS; // Same as fragment for simplicity muxer->latest_acodec_ = SrsAudioCodecIdAAC; muxer->latest_vcodec_ = SrsVideoCodecIdAVC; muxer->writer_ = new MockSrsFileWriter(); @@ -953,22 +953,22 @@ VOID TEST(HlsMuxerTest, SegmentOverflowAndPureAudio) // Test is_segment_overflow: Segment too small (< 2 * SRS_HLS_SEGMENT_MIN_DURATION) segment->append(0); - segment->append(50); // 50ms duration, too small + segment->append(50); // 50ms duration, too small EXPECT_FALSE(muxer->is_segment_overflow()); // Test is_segment_overflow: Segment duration just below threshold segment->append(0); - segment->append(9000); // 9 seconds, below 10 seconds threshold + segment->append(9000); // 9 seconds, below 10 seconds threshold EXPECT_FALSE(muxer->is_segment_overflow()); // Test is_segment_overflow: Segment duration at threshold segment->append(0); - segment->append(10000); // 10 seconds, at threshold + segment->append(10000); // 10 seconds, at threshold EXPECT_TRUE(muxer->is_segment_overflow()); // Test is_segment_overflow: Segment duration above threshold segment->append(0); - segment->append(12000); // 12 seconds, above threshold + segment->append(12000); // 12 seconds, above threshold EXPECT_TRUE(muxer->is_segment_overflow()); // Test wait_keyframe @@ -984,22 +984,22 @@ VOID TEST(HlsMuxerTest, SegmentOverflowAndPureAudio) // Test is_segment_absolutely_overflow: Segment too small segment->append(0); - segment->append(50); // 50ms duration, too small + segment->append(50); // 50ms duration, too small EXPECT_FALSE(muxer->is_segment_absolutely_overflow()); // Test is_segment_absolutely_overflow: Below absolute overflow threshold (2x fragment) segment->append(0); - segment->append(15000); // 15 seconds, below 20 seconds (2x 10s) + segment->append(15000); // 15 seconds, below 20 seconds (2x 10s) EXPECT_FALSE(muxer->is_segment_absolutely_overflow()); // Test is_segment_absolutely_overflow: At absolute overflow threshold segment->append(0); - segment->append(20000); // 20 seconds, at 2x threshold + segment->append(20000); // 20 seconds, at 2x threshold EXPECT_TRUE(muxer->is_segment_absolutely_overflow()); // Test is_segment_absolutely_overflow: Above absolute overflow threshold segment->append(0); - segment->append(25000); // 25 seconds, above 2x threshold + segment->append(25000); // 25 seconds, above 2x threshold EXPECT_TRUE(muxer->is_segment_absolutely_overflow()); // Test pure_audio: With video codec enabled (not pure audio) @@ -1121,8 +1121,16 @@ VOID TEST(AppHlsTest, HlsControllerWriteAudioTypicalScenario) // Create audio payload (AAC raw data) - must be heap allocated for wrap() char *audio_data = new char[10]; - audio_data[0] = 0x01; audio_data[1] = 0x02; audio_data[2] = 0x03; audio_data[3] = 0x04; audio_data[4] = 0x05; - audio_data[5] = 0x06; audio_data[6] = 0x07; audio_data[7] = 0x08; audio_data[8] = 0x09; audio_data[9] = 0x0a; + audio_data[0] = 0x01; + audio_data[1] = 0x02; + audio_data[2] = 0x03; + audio_data[3] = 0x04; + audio_data[4] = 0x05; + audio_data[5] = 0x06; + audio_data[6] = 0x07; + audio_data[7] = 0x08; + audio_data[8] = 0x09; + audio_data[9] = 0x0a; audio_packet->wrap(audio_data, 10); // Add sample to format->audio_ @@ -1150,8 +1158,16 @@ VOID TEST(AppHlsTest, HlsControllerWriteAudioTypicalScenario) // Create audio payload - must be heap allocated for wrap() char *audio_data2 = new char[10]; - audio_data2[0] = 0x0b; audio_data2[1] = 0x0c; audio_data2[2] = 0x0d; audio_data2[3] = 0x0e; audio_data2[4] = 0x0f; - audio_data2[5] = 0x10; audio_data2[6] = 0x11; audio_data2[7] = 0x12; audio_data2[8] = 0x13; audio_data2[9] = 0x14; + audio_data2[0] = 0x0b; + audio_data2[1] = 0x0c; + audio_data2[2] = 0x0d; + audio_data2[3] = 0x0e; + audio_data2[4] = 0x0f; + audio_data2[5] = 0x10; + audio_data2[6] = 0x11; + audio_data2[7] = 0x12; + audio_data2[8] = 0x13; + audio_data2[9] = 0x14; audio_packet2->wrap(audio_data2, 10); format->audio_->nb_samples_ = 1; @@ -1328,19 +1344,19 @@ VOID TEST(AppHlsTest, HlsMuxerDoRefreshM3u8TypicalScenario) segment1->sequence_no_ = 100; segment1->uri_ = "stream1-100.ts"; segment1->append(0); - segment1->append(10000); // 10 seconds duration + segment1->append(10000); // 10 seconds duration SrsHlsSegment *segment2 = new SrsHlsSegment(context.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC, fw2.get()); segment2->sequence_no_ = 101; segment2->uri_ = "stream1-101.ts"; segment2->append(10000); - segment2->append(20000); // 10 seconds duration + segment2->append(20000); // 10 seconds duration SrsHlsSegment *segment3 = new SrsHlsSegment(context.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC, fw3.get()); segment3->sequence_no_ = 102; segment3->uri_ = "stream1-102.ts"; segment3->append(20000); - segment3->append(28000); // 8 seconds duration + segment3->append(28000); // 8 seconds duration // Add segments to the fragment window (ownership transferred to segments_) muxer->segments_->append(segment1); @@ -1406,7 +1422,7 @@ VOID TEST(AppHlsTest, HlsControllerSelectionTypicalScenario) segment->sequence_no_ = 42; segment->uri_ = "stream1-42.ts"; segment->append(0); - segment->append(10000); // 10 seconds duration in milliseconds + segment->append(10000); // 10 seconds duration in milliseconds // Set the current segment in the muxer controller->muxer_->current_ = segment; @@ -1625,7 +1641,6 @@ VOID TEST(AppHlsTest, HlsControllerWriteVideoTypicalScenario) EXPECT_EQ(SrsVideoCodecIdAVC, controller->muxer_->latest_vcodec()); } - // Unit test for SrsHlsController::reap_segment typical scenario VOID TEST(AppHlsTest, HlsControllerReapSegmentTypicalScenario) { @@ -1937,8 +1952,16 @@ VOID TEST(AppHlsTest, HlsOnAudioTypicalScenario) // Create audio payload (AAC raw data) - must be heap allocated for wrap() char *audio_data = new char[10]; - audio_data[0] = 0x01; audio_data[1] = 0x02; audio_data[2] = 0x03; audio_data[3] = 0x04; audio_data[4] = 0x05; - audio_data[5] = 0x06; audio_data[6] = 0x07; audio_data[7] = 0x08; audio_data[8] = 0x09; audio_data[9] = 0x0a; + audio_data[0] = 0x01; + audio_data[1] = 0x02; + audio_data[2] = 0x03; + audio_data[3] = 0x04; + audio_data[4] = 0x05; + audio_data[5] = 0x06; + audio_data[6] = 0x07; + audio_data[7] = 0x08; + audio_data[8] = 0x09; + audio_data[9] = 0x0a; audio_packet->wrap(audio_data, 10); // Add sample to format->audio_ diff --git a/trunk/src/utest/srs_utest_app9.cpp b/trunk/src/utest/srs_utest_app9.cpp new file mode 100644 index 000000000..4e88beffd --- /dev/null +++ b/trunk/src/utest/srs_utest_app9.cpp @@ -0,0 +1,3310 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// +#include + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MockMediaPacketForJitter::MockMediaPacketForJitter(int64_t timestamp, bool is_av) +{ + timestamp_ = timestamp; + + // Create sample payload + char *payload = new char[128]; + memset(payload, 0x00, 128); + SrsMediaPacket::wrap(payload, 128); + + if (is_av) { + message_type_ = SrsFrameTypeVideo; + } else { + message_type_ = SrsFrameTypeScript; + } +} + +MockMediaPacketForJitter::~MockMediaPacketForJitter() +{ +} + +MockLiveSourceForQueue::MockLiveSourceForQueue() +{ +} + +MockLiveSourceForQueue::~MockLiveSourceForQueue() +{ +} + +void MockLiveSourceForQueue::on_consumer_destroy(SrsLiveConsumer *consumer) +{ + // Do nothing in mock +} + +srs_error_t MockLiveSourceForQueue::initialize(SrsSharedPtr wrapper, ISrsRequest *r) +{ + // Mock initialize - do nothing and return success + return srs_success; +} + +void MockLiveSourceForQueue::update_auth(ISrsRequest *r) +{ + // Mock update_auth - do nothing to avoid accessing null req_ +} + +MockLiveConsumerForQueue::MockLiveConsumerForQueue(MockLiveSourceForQueue *source) + : SrsLiveConsumer(source) +{ + enqueue_count_ = 0; +} + +MockLiveConsumerForQueue::~MockLiveConsumerForQueue() +{ +} + +srs_error_t MockLiveConsumerForQueue::enqueue(SrsMediaPacket *shared_msg, bool atc, SrsRtmpJitterAlgorithm ag) +{ + enqueue_count_++; + enqueued_timestamps_.push_back(shared_msg->timestamp_); + return srs_success; +} + +MockH264VideoPacket::MockH264VideoPacket(bool is_keyframe) +{ + timestamp_ = 0; + message_type_ = SrsFrameTypeVideo; + + // Create H.264 video payload + // Format: [frame_type_and_codec_id][avc_packet_type][composition_time] + // For H.264: codec_id = 7 (0x07) + // For keyframe: frame_type = 1 (0x10), for inter frame: frame_type = 2 (0x20) + char *payload = new char[128]; + memset(payload, 0x00, 128); + + if (is_keyframe) { + payload[0] = 0x17; // keyframe + H.264 (0x10 | 0x07) + } else { + payload[0] = 0x27; // inter frame + H.264 (0x20 | 0x07) + } + payload[1] = 0x01; // AVC NALU (not sequence header) + + SrsMediaPacket::wrap(payload, 128); +} + +MockH264VideoPacket::~MockH264VideoPacket() +{ +} + +MockHourGlassForSourceManager::MockHourGlassForSourceManager() +{ + tick_event_ = 0; + tick_interval_ = 0; + tick_count_ = 0; + start_count_ = 0; + tick_error_ = srs_success; + start_error_ = srs_success; +} + +MockHourGlassForSourceManager::~MockHourGlassForSourceManager() +{ +} + +srs_error_t MockHourGlassForSourceManager::start() +{ + start_count_++; + return srs_error_copy(start_error_); +} + +void MockHourGlassForSourceManager::stop() +{ + // Do nothing in mock +} + +srs_error_t MockHourGlassForSourceManager::tick(srs_utime_t interval) +{ + tick_count_++; + tick_interval_ = interval; + return srs_error_copy(tick_error_); +} + +srs_error_t MockHourGlassForSourceManager::tick(int event, srs_utime_t interval) +{ + tick_count_++; + tick_event_ = event; + tick_interval_ = interval; + return srs_error_copy(tick_error_); +} + +void MockHourGlassForSourceManager::untick(int event) +{ + // Do nothing in mock +} + +MockAppFactoryForSourceManager::MockAppFactoryForSourceManager() +{ + create_live_source_count_ = 0; +} + +MockAppFactoryForSourceManager::~MockAppFactoryForSourceManager() +{ +} + +SrsLiveSource *MockAppFactoryForSourceManager::create_live_source() +{ + create_live_source_count_++; + return new MockLiveSourceForQueue(); +} + +MockAudioPacket::MockAudioPacket() +{ + timestamp_ = 0; + message_type_ = SrsFrameTypeAudio; + + // Create audio payload + char *payload = new char[128]; + memset(payload, 0x00, 128); + + SrsMediaPacket::wrap(payload, 128); +} + +MockAudioPacket::~MockAudioPacket() +{ +} + +VOID TEST(RtmpJitterTest, CorrectZeroAlgorithmWaitForFirstPacket) +{ + srs_error_t err = srs_success; + + // Test ZERO algorithm: start at zero, but don't ensure monotonically increasing + // The algorithm "waits" for the first packet to establish the base timestamp + SrsUniquePtr jitter(new SrsRtmpJitter()); + + // Verify initial state: last_pkt_correct_time_ is -1 (waiting for first packet) + EXPECT_EQ(-1, jitter->get_time()); + + // Test 1: First packet at timestamp 5000ms + // This is the "wait" - the algorithm waits for the first packet to set the base time + // When last_pkt_correct_time_ == -1, it stores msg->timestamp_ as the base + // Then subtracts it, making the first packet start at 0 + SrsUniquePtr pkt1(new MockMediaPacketForJitter(5000, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt1.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(0, pkt1->timestamp_); // 5000 - 5000 = 0 + EXPECT_EQ(5000, jitter->get_time()); // Base time is now set to 5000 + + // Test 2: Second packet at timestamp 5040ms (40ms after first) + // Now that base time is set (no longer waiting), subtract base time + // Result: 5040 - 5000 = 40ms + SrsUniquePtr pkt2(new MockMediaPacketForJitter(5040, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt2.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(40, pkt2->timestamp_); // 5040 - 5000 = 40 + EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000 + + // Test 3: Third packet at timestamp 5100ms (60ms after second) + // Continue subtracting the same base time + // Result: 5100 - 5000 = 100ms + SrsUniquePtr pkt3(new MockMediaPacketForJitter(5100, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt3.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(100, pkt3->timestamp_); // 5100 - 5000 = 100 + EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000 + + // Test 4: Packet with timestamp jump (timestamp 6000ms, 900ms jump) + // ZERO algorithm does NOT correct jitter, just subtracts base time + // Result: 6000 - 5000 = 1000ms (large jump is preserved) + SrsUniquePtr pkt4(new MockMediaPacketForJitter(6000, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt4.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(1000, pkt4->timestamp_); // 6000 - 5000 = 1000 (jitter preserved) + EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000 + + // Test 5: Packet with timestamp going backwards (timestamp 5500ms) + // ZERO algorithm does NOT ensure monotonically increasing + // Result: 5500 - 5000 = 500ms (can go backwards relative to previous packet) + SrsUniquePtr pkt5(new MockMediaPacketForJitter(5500, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt5.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(500, pkt5->timestamp_); // 5500 - 5000 = 500 (backwards allowed) + EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000 + + // Test 6: Packet with very large timestamp (timestamp 100000ms) + // ZERO algorithm just subtracts base time, no correction + // Result: 100000 - 5000 = 95000ms + SrsUniquePtr pkt6(new MockMediaPacketForJitter(100000, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt6.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(95000, pkt6->timestamp_); // 100000 - 5000 = 95000 + EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000 + + // Test 7: Edge case - packet with timestamp 0 + // Result: 0 - 5000 = -5000 (negative timestamp allowed in ZERO algorithm) + SrsUniquePtr pkt7(new MockMediaPacketForJitter(0, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt7.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(-5000, pkt7->timestamp_); // 0 - 5000 = -5000 (negative allowed) + EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000 + + // Test 8: Verify the "wait" behavior - create new jitter with timestamp 0 as first packet + SrsUniquePtr jitter2(new SrsRtmpJitter()); + EXPECT_EQ(-1, jitter2->get_time()); // Initially waiting + + SrsUniquePtr pkt8(new MockMediaPacketForJitter(0, true)); + HELPER_EXPECT_SUCCESS(jitter2->correct(pkt8.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(0, pkt8->timestamp_); // 0 - 0 = 0 + EXPECT_EQ(0, jitter2->get_time()); // Base time is now 0 + + // Subsequent packet should be relative to base time 0 + SrsUniquePtr pkt9(new MockMediaPacketForJitter(1000, true)); + HELPER_EXPECT_SUCCESS(jitter2->correct(pkt9.get(), SrsRtmpJitterAlgorithmZERO)); + EXPECT_EQ(1000, pkt9->timestamp_); // 1000 - 0 = 1000 + EXPECT_EQ(0, jitter2->get_time()); // Base time remains 0 +} + +VOID TEST(RtmpJitterTest, CorrectTypicalScenario) +{ + srs_error_t err = srs_success; + + // Test FULL algorithm with typical use scenario + SrsUniquePtr jitter(new SrsRtmpJitter()); + + // Test 1: First video packet at timestamp 1000 + // For first packet: last_pkt_time_ = 0, delta = 1000 - 0 = 1000 + // Since delta > 250ms, use default 10ms + // last_pkt_correct_time_ = max(0, -1 + 10) = 9 + SrsUniquePtr pkt1(new MockMediaPacketForJitter(1000, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt1.get(), SrsRtmpJitterAlgorithmFULL)); + EXPECT_EQ(9, pkt1->timestamp_); + + // Test 2: Second video packet at timestamp 1040 (40ms delta, normal) + // delta = 1040 - 1000 = 40ms (valid) + // last_pkt_correct_time_ = max(0, 9 + 40) = 49 + SrsUniquePtr pkt2(new MockMediaPacketForJitter(1040, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt2.get(), SrsRtmpJitterAlgorithmFULL)); + EXPECT_EQ(49, pkt2->timestamp_); + + // Test 3: Third video packet at timestamp 1080 (40ms delta, normal) + // delta = 1080 - 1040 = 40ms (valid) + // last_pkt_correct_time_ = max(0, 49 + 40) = 89 + SrsUniquePtr pkt3(new MockMediaPacketForJitter(1080, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt3.get(), SrsRtmpJitterAlgorithmFULL)); + EXPECT_EQ(89, pkt3->timestamp_); + + // Test 4: Packet with large jitter (timestamp 1500, delta 420ms > 250ms threshold) + // delta = 1500 - 1080 = 420ms (> 250ms threshold) + // Use default 10ms delta + // last_pkt_correct_time_ = max(0, 89 + 10) = 99 + SrsUniquePtr pkt4(new MockMediaPacketForJitter(1500, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt4.get(), SrsRtmpJitterAlgorithmFULL)); + EXPECT_EQ(99, pkt4->timestamp_); + + // Test 5: Packet with negative jitter (timestamp 1450, delta -50ms) + // delta = 1450 - 1500 = -50ms (within -250ms to 250ms range, so valid) + // last_pkt_correct_time_ = max(0, 99 + (-50)) = 49 + SrsUniquePtr pkt5(new MockMediaPacketForJitter(1450, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt5.get(), SrsRtmpJitterAlgorithmFULL)); + EXPECT_EQ(49, pkt5->timestamp_); + + // Test 6: Metadata packet (non-AV) + // Metadata should always be set to 0, doesn't update last_pkt_time_ + SrsUniquePtr pkt6(new MockMediaPacketForJitter(2000, false)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt6.get(), SrsRtmpJitterAlgorithmFULL)); + EXPECT_EQ(0, pkt6->timestamp_); + + // Test 7: Continue with normal packet after metadata + // delta = 1490 - 1450 = 40ms (valid) + // last_pkt_correct_time_ = max(0, 49 + 40) = 89 + SrsUniquePtr pkt7(new MockMediaPacketForJitter(1490, true)); + HELPER_EXPECT_SUCCESS(jitter->correct(pkt7.get(), SrsRtmpJitterAlgorithmFULL)); + EXPECT_EQ(89, pkt7->timestamp_); +} + +#ifdef SRS_PERF_QUEUE_FAST_VECTOR +VOID TEST(FastVectorTest, TypicalUseScenario) +{ + // Create a fast vector instance + SrsUniquePtr vec(new SrsFastVector()); + + // Test 1: Initial state - should be empty + EXPECT_EQ(0, vec->size()); + EXPECT_EQ(0, vec->begin()); + EXPECT_EQ(0, vec->end()); + + // Test 2: Push back some packets + for (int i = 0; i < 5; i++) { + SrsMediaPacket *pkt = new SrsMediaPacket(); + pkt->timestamp_ = i * 40; + char *payload = new char[128]; + memset(payload, 0x00, 128); + pkt->wrap(payload, 128); + vec->push_back(pkt); + } + + // Verify size after push_back + EXPECT_EQ(5, vec->size()); + EXPECT_EQ(0, vec->begin()); + EXPECT_EQ(5, vec->end()); + + // Test 3: Access elements using at() + for (int i = 0; i < 5; i++) { + SrsMediaPacket *pkt = vec->at(i); + EXPECT_EQ(i * 40, pkt->timestamp_); + } + + // Test 4: Erase some elements (erase first 2 elements) + vec->erase(0, 2); + EXPECT_EQ(3, vec->size()); + + // Verify remaining elements shifted correctly + EXPECT_EQ(80, vec->at(0)->timestamp_); // Was at index 2 + EXPECT_EQ(120, vec->at(1)->timestamp_); // Was at index 3 + EXPECT_EQ(160, vec->at(2)->timestamp_); // Was at index 4 + + // Test 5: Push back more packets to trigger array expansion + for (int i = 0; i < 10; i++) { + SrsMediaPacket *pkt = new SrsMediaPacket(); + pkt->timestamp_ = 200 + i * 40; + char *payload = new char[128]; + memset(payload, 0x00, 128); + pkt->wrap(payload, 128); + vec->push_back(pkt); + } + + // Verify size after expansion + EXPECT_EQ(13, vec->size()); + + // Test 6: Clear the vector (sets count to 0 but doesn't free packets) + vec->clear(); + EXPECT_EQ(0, vec->size()); + EXPECT_EQ(0, vec->begin()); + EXPECT_EQ(0, vec->end()); +} +#endif + +VOID TEST(MessageQueueTest, EnqueueTypicalScenario) +{ + srs_error_t err = srs_success; + + // Create message queue with ignore_shrink=true to avoid log output during test + SrsUniquePtr queue(new SrsMessageQueue(true)); + + // Set queue size to 5 seconds (5000ms) + queue->set_queue_size(5 * SRS_UTIME_SECONDS); + + // Test 1: Enqueue first video packet at timestamp 1000ms + // Note: enqueue takes ownership of the packet, so we release it from unique_ptr + MockMediaPacketForJitter *pkt1 = new MockMediaPacketForJitter(1000, true); + bool is_overflow = false; + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt1, &is_overflow)); + EXPECT_FALSE(is_overflow); + EXPECT_EQ(1, queue->size()); + EXPECT_EQ(0, srsu2msi(queue->duration())); + + // Test 2: Enqueue second video packet at timestamp 2000ms (1 second later) + MockMediaPacketForJitter *pkt2 = new MockMediaPacketForJitter(2000, true); + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt2, &is_overflow)); + EXPECT_FALSE(is_overflow); + EXPECT_EQ(2, queue->size()); + EXPECT_EQ(1000, srsu2msi(queue->duration())); + + // Test 3: Enqueue third video packet at timestamp 4000ms (2 seconds later) + MockMediaPacketForJitter *pkt3 = new MockMediaPacketForJitter(4000, true); + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt3, &is_overflow)); + EXPECT_FALSE(is_overflow); + EXPECT_EQ(3, queue->size()); + EXPECT_EQ(3000, srsu2msi(queue->duration())); + + // Test 4: Enqueue fourth video packet at timestamp 7000ms (3 seconds later) + // Total duration is now 6 seconds, which exceeds max_queue_size (5 seconds) + // This should trigger shrink and set is_overflow to true + MockMediaPacketForJitter *pkt4 = new MockMediaPacketForJitter(7000, true); + is_overflow = false; + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt4, &is_overflow)); + EXPECT_TRUE(is_overflow); + // After shrink, queue should have fewer messages + EXPECT_LT(queue->size(), 4); + + // Test 5: Verify duration is within max_queue_size after shrink + EXPECT_LE(queue->duration(), 5 * SRS_UTIME_SECONDS); +} + +VOID TEST(MessageQueueTest, DumpPacketsTypicalScenario) +{ + srs_error_t err = srs_success; + + // Create a message queue + SrsUniquePtr queue(new SrsMessageQueue()); + + // Test 1: dump_packets with array - typical scenario with 3 packets + if (true) { + // Enqueue 3 video packets + MockMediaPacketForJitter *pkt1 = new MockMediaPacketForJitter(1000, true); + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt1, NULL)); + + MockMediaPacketForJitter *pkt2 = new MockMediaPacketForJitter(2000, true); + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt2, NULL)); + + MockMediaPacketForJitter *pkt3 = new MockMediaPacketForJitter(3000, true); + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt3, NULL)); + + EXPECT_EQ(3, queue->size()); + + // Dump packets to array + const int max_count = 10; + SrsMediaPacket *pmsgs[max_count]; + int count = 0; + HELPER_EXPECT_SUCCESS(queue->dump_packets(max_count, pmsgs, count)); + + // Verify all 3 packets were dumped + EXPECT_EQ(3, count); + EXPECT_EQ(1000, pmsgs[0]->timestamp_); + EXPECT_EQ(2000, pmsgs[1]->timestamp_); + EXPECT_EQ(3000, pmsgs[2]->timestamp_); + + // Verify queue is now empty after dump + EXPECT_EQ(0, queue->size()); + + // Free the dumped packets + for (int i = 0; i < count; i++) { + srs_freep(pmsgs[i]); + } + } + + // Test 2: dump_packets with consumer - typical scenario with 2 packets + if (true) { + // Enqueue 2 audio packets + MockMediaPacketForJitter *pkt1 = new MockMediaPacketForJitter(4000, true); + pkt1->message_type_ = SrsFrameTypeAudio; + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt1, NULL)); + + MockMediaPacketForJitter *pkt2 = new MockMediaPacketForJitter(5000, true); + pkt2->message_type_ = SrsFrameTypeAudio; + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt2, NULL)); + + EXPECT_EQ(2, queue->size()); + + // Create mock source and consumer + SrsUniquePtr source(new MockLiveSourceForQueue()); + SrsUniquePtr consumer(new MockLiveConsumerForQueue(source.get())); + + // Dump packets to consumer + HELPER_EXPECT_SUCCESS(queue->dump_packets(consumer.get(), true, SrsRtmpJitterAlgorithmFULL)); + + // Verify consumer received all packets + EXPECT_EQ(2, consumer->enqueue_count_); + EXPECT_EQ(4000, consumer->enqueued_timestamps_[0]); + EXPECT_EQ(5000, consumer->enqueued_timestamps_[1]); + + // Note: Queue still contains packets after dump_packets(consumer) call + // because this method doesn't clear the queue + EXPECT_EQ(2, queue->size()); + } +} + +VOID TEST(MessageQueueTest, DumpPacketsPartialErase) +{ + srs_error_t err = srs_success; + + // Create a message queue + SrsUniquePtr queue(new SrsMessageQueue()); + + // Enqueue 10 video packets with timestamps 1000, 2000, ..., 10000 + // This creates a scenario where we have MORE messages than we will dump + for (int i = 1; i <= 10; i++) { + MockMediaPacketForJitter *pkt = new MockMediaPacketForJitter(i * 1000, true); + HELPER_EXPECT_SUCCESS(queue->enqueue(pkt, NULL)); + } + + // Verify all 10 packets are in the queue + EXPECT_EQ(10, queue->size()); + + // Dump only 3 packets (max_count=3), which is LESS than the total number of messages (10) + // This will trigger the else branch: msgs_.erase(msgs_.begin(), msgs_.begin() + count) + // because count (3) < nb_msgs (10) + const int max_count = 3; + SrsMediaPacket *pmsgs[max_count]; + int count = 0; + HELPER_EXPECT_SUCCESS(queue->dump_packets(max_count, pmsgs, count)); + + // Verify exactly 3 packets were dumped (the first 3) + EXPECT_EQ(3, count); + EXPECT_EQ(1000, pmsgs[0]->timestamp_); + EXPECT_EQ(2000, pmsgs[1]->timestamp_); + EXPECT_EQ(3000, pmsgs[2]->timestamp_); + + // Verify 7 packets remain in the queue (10 - 3 = 7) + // This confirms that msgs_.erase() correctly removed only the first 3 elements + EXPECT_EQ(7, queue->size()); + + // Free the dumped packets + for (int i = 0; i < count; i++) { + srs_freep(pmsgs[i]); + } + + // Dump the remaining packets to verify they are the correct ones (4000-10000) + const int max_count2 = 10; + SrsMediaPacket *pmsgs2[max_count2]; + int count2 = 0; + HELPER_EXPECT_SUCCESS(queue->dump_packets(max_count2, pmsgs2, count2)); + + // Verify the remaining 7 packets have the correct timestamps + EXPECT_EQ(7, count2); + EXPECT_EQ(4000, pmsgs2[0]->timestamp_); + EXPECT_EQ(5000, pmsgs2[1]->timestamp_); + EXPECT_EQ(6000, pmsgs2[2]->timestamp_); + EXPECT_EQ(7000, pmsgs2[3]->timestamp_); + EXPECT_EQ(8000, pmsgs2[4]->timestamp_); + EXPECT_EQ(9000, pmsgs2[5]->timestamp_); + EXPECT_EQ(10000, pmsgs2[6]->timestamp_); + + // Verify queue is now empty + EXPECT_EQ(0, queue->size()); + + // Free the remaining packets + for (int i = 0; i < count2; i++) { + srs_freep(pmsgs2[i]); + } +} + +VOID TEST(MessageQueueTest, ShrinkAndClear) +{ + srs_error_t err; + + // Create message queue + SrsUniquePtr queue(new SrsMessageQueue(true)); + + // Set queue size to trigger shrink + queue->set_queue_size(10 * SRS_UTIME_SECONDS); + + // Create video sequence header packet (0x17 = keyframe + AVC, 0x00 = sequence header) + SrsMediaPacket *video_sh = new SrsMediaPacket(); + char *video_sh_data = new char[10]; + video_sh_data[0] = 0x17; // keyframe + AVC + video_sh_data[1] = 0x00; // sequence header + for (int i = 2; i < 10; i++) { + video_sh_data[i] = 0x00; + } + video_sh->wrap(video_sh_data, 10); + video_sh->timestamp_ = 1000; + video_sh->message_type_ = SrsFrameTypeVideo; + HELPER_EXPECT_SUCCESS(queue->enqueue(video_sh, NULL)); + + // Create audio sequence header packet (0xa0 = AAC, 0x00 = sequence header) + SrsMediaPacket *audio_sh = new SrsMediaPacket(); + char *audio_sh_data = new char[10]; + audio_sh_data[0] = 0xa0; // AAC + audio_sh_data[1] = 0x00; // sequence header + for (int i = 2; i < 10; i++) { + audio_sh_data[i] = 0x00; + } + audio_sh->wrap(audio_sh_data, 10); + audio_sh->timestamp_ = 1000; + audio_sh->message_type_ = SrsFrameTypeAudio; + HELPER_EXPECT_SUCCESS(queue->enqueue(audio_sh, NULL)); + + // Create regular video packet (0x27 = inter frame + AVC, 0x01 = NALU) + SrsMediaPacket *video_pkt = new SrsMediaPacket(); + char *video_pkt_data = new char[10]; + video_pkt_data[0] = 0x27; // inter frame + AVC + video_pkt_data[1] = 0x01; // NALU + for (int i = 2; i < 10; i++) { + video_pkt_data[i] = 0x01; + } + video_pkt->wrap(video_pkt_data, 10); + video_pkt->timestamp_ = 2000; + video_pkt->message_type_ = SrsFrameTypeVideo; + HELPER_EXPECT_SUCCESS(queue->enqueue(video_pkt, NULL)); + + // Create regular audio packet (0xa0 = AAC, 0x01 = raw data) + SrsMediaPacket *audio_pkt = new SrsMediaPacket(); + char *audio_pkt_data = new char[10]; + audio_pkt_data[0] = 0xa0; // AAC + audio_pkt_data[1] = 0x01; // raw data + for (int i = 2; i < 10; i++) { + audio_pkt_data[i] = 0x01; + } + audio_pkt->wrap(audio_pkt_data, 10); + audio_pkt->timestamp_ = 2000; + audio_pkt->message_type_ = SrsFrameTypeAudio; + HELPER_EXPECT_SUCCESS(queue->enqueue(audio_pkt, NULL)); + + // Verify queue has 4 messages + EXPECT_EQ(4, queue->size()); + + // Test shrink() - should keep only sequence headers and update their timestamps + queue->shrink(); + + // After shrink, only sequence headers should remain + EXPECT_EQ(2, queue->size()); + + // Test clear() - should remove all messages + queue->clear(); + + // After clear, queue should be empty + EXPECT_EQ(0, queue->size()); +} + +VOID TEST(SrsLiveConsumerTest, TypicalUsage) +{ + srs_error_t err; + + // Create mock source + SrsUniquePtr mock_source(new MockLiveSourceForQueue()); + + // Create consumer with mock source - tests constructor + SrsUniquePtr consumer(new SrsLiveConsumer(mock_source.get())); + + // Test set_queue_size - typical queue size is 10 seconds + srs_utime_t queue_size = 10 * SRS_UTIME_SECONDS; + consumer->set_queue_size(queue_size); + + // Test update_source_id - should set internal flag + consumer->update_source_id(); + + // Test get_time - returns jitter's last_pkt_correct_time_ which is -1 initially + EXPECT_EQ(-1, consumer->get_time()); + + // Enqueue a test packet to update jitter time + SrsUniquePtr pkt(new MockMediaPacketForJitter(1000, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt.get(), false, SrsRtmpJitterAlgorithmFULL)); + + // After enqueuing with FULL jitter algorithm, get_time returns corrected time + // The jitter algorithm detects large delta (1000 - 0) and uses DEFAULT_FRAME_TIME_MS (10ms) + // So last_pkt_correct_time_ = max(0, -1 + 10) = 9 + EXPECT_EQ(9, consumer->get_time()); + + // Destructor will be called automatically and should invoke on_consumer_destroy +} + +VOID TEST(SrsLiveConsumerEnqueueTest, TypicalScenario) +{ + srs_error_t err; + + // Create mock source + SrsUniquePtr mock_source(new MockLiveSourceForQueue()); + + // Create consumer with mock source + SrsUniquePtr consumer(new SrsLiveConsumer(mock_source.get())); + + // Set typical queue size (10 seconds) + consumer->set_queue_size(10 * SRS_UTIME_SECONDS); + + // Test typical scenario: enqueue video packet with jitter correction (atc=false) + // Create a video packet at timestamp 1000ms + SrsUniquePtr pkt1(new MockMediaPacketForJitter(1000, true)); + + // Enqueue with jitter correction enabled (atc=false, FULL algorithm) + // This tests the main flow: + // 1. Copy the message (shared_msg->copy()) + // 2. Apply jitter correction (!atc, so jitter_->correct() is called) + // 3. Enqueue to queue (queue_->enqueue()) + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1.get(), false, SrsRtmpJitterAlgorithmFULL)); + + // Verify jitter correction was applied - first packet gets corrected to 9ms + // (large delta detected, uses DEFAULT_FRAME_TIME_MS=10ms, result: max(0, -1+10)=9) + EXPECT_EQ(9, consumer->get_time()); + + // Enqueue second video packet at timestamp 1040ms (40ms delta, normal) + SrsUniquePtr pkt2(new MockMediaPacketForJitter(1040, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2.get(), false, SrsRtmpJitterAlgorithmFULL)); + + // Verify jitter correction: 9 + 40 = 49ms + EXPECT_EQ(49, consumer->get_time()); + + // Test ATC mode: enqueue without jitter correction (atc=true) + // Create packet at timestamp 2000ms + SrsUniquePtr pkt3(new MockMediaPacketForJitter(2000, true)); + + // Enqueue with ATC enabled (atc=true) - skips jitter correction + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt3.get(), true, SrsRtmpJitterAlgorithmFULL)); + + // Verify jitter time unchanged (still 49ms) because ATC skips jitter correction + EXPECT_EQ(49, consumer->get_time()); +} + +VOID TEST(SrsLiveConsumerDumpPacketsTest, TypicalScenario) +{ + srs_error_t err; + + // Create mock source with source IDs + SrsUniquePtr mock_source(new MockLiveSourceForQueue()); + + // Create consumer with mock source + SrsUniquePtr consumer(new SrsLiveConsumer(mock_source.get())); + + // Set typical queue size (10 seconds) + consumer->set_queue_size(10 * SRS_UTIME_SECONDS); + + // Enqueue 3 video packets to the consumer's internal queue + SrsUniquePtr pkt1(new MockMediaPacketForJitter(1000, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1.get(), false, SrsRtmpJitterAlgorithmFULL)); + + SrsUniquePtr pkt2(new MockMediaPacketForJitter(1040, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2.get(), false, SrsRtmpJitterAlgorithmFULL)); + + SrsUniquePtr pkt3(new MockMediaPacketForJitter(1080, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt3.get(), false, SrsRtmpJitterAlgorithmFULL)); + + // Create SrsMessageArray to receive dumped packets + const int max_msgs = 10; + SrsUniquePtr msgs(new SrsMessageArray(max_msgs)); + + // Test typical scenario: dump packets from consumer + int count = 0; + HELPER_EXPECT_SUCCESS(consumer->dump_packets(msgs.get(), count)); + + // Verify all 3 packets were dumped + EXPECT_EQ(3, count); + EXPECT_EQ(9, msgs->msgs_[0]->timestamp_); // First packet corrected to 9ms + EXPECT_EQ(49, msgs->msgs_[1]->timestamp_); // Second packet corrected to 49ms + EXPECT_EQ(89, msgs->msgs_[2]->timestamp_); // Third packet corrected to 89ms + + // Free the dumped packets + msgs->free(count); +} + +#ifdef SRS_PERF_QUEUE_COND_WAIT +VOID TEST(SrsLiveConsumerWaitTest, TypicalScenario) +{ + srs_error_t err; + + // Create mock source + SrsUniquePtr mock_source(new MockLiveSourceForQueue()); + + // Create consumer with mock source + SrsUniquePtr consumer(new SrsLiveConsumer(mock_source.get())); + + // Set typical queue size (10 seconds) + consumer->set_queue_size(10 * SRS_UTIME_SECONDS); + + // Enqueue 3 video packets to the consumer's internal queue + // Each packet is 40ms apart + SrsUniquePtr pkt1(new MockMediaPacketForJitter(1000, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1.get(), false, SrsRtmpJitterAlgorithmFULL)); + + SrsUniquePtr pkt2(new MockMediaPacketForJitter(1040, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2.get(), false, SrsRtmpJitterAlgorithmFULL)); + + SrsUniquePtr pkt3(new MockMediaPacketForJitter(1080, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt3.get(), false, SrsRtmpJitterAlgorithmFULL)); + + // Test typical scenario: wait returns immediately when queue has enough messages and duration + // Queue has 3 messages with duration ~80ms (89ms - 9ms) + // Request wait for 1 message and 50ms duration + // Since queue has 3 > 1 messages and duration 80ms > 50ms, wait should return immediately + consumer->wait(1, 50 * SRS_UTIME_MILLISECONDS); + + // Verify consumer is still functional after wait + // Create SrsMessageArray to receive dumped packets + const int max_msgs = 10; + SrsUniquePtr msgs(new SrsMessageArray(max_msgs)); + + // Dump packets to verify queue is intact + int count = 0; + HELPER_EXPECT_SUCCESS(consumer->dump_packets(msgs.get(), count)); + + // Verify all 3 packets are still in queue + EXPECT_EQ(3, count); + + // Free the dumped packets + msgs->free(count); +} +#endif + +VOID TEST(LiveConsumerTest, OnPlayClientPauseTypicalScenario) +{ + srs_error_t err = srs_success; + + // Create mock live source + SrsUniquePtr source(new MockLiveSourceForQueue()); + + // Create live consumer + SrsUniquePtr consumer(new SrsLiveConsumer(source.get())); + + // Test typical scenario: pause the consumer + HELPER_EXPECT_SUCCESS(consumer->on_play_client_pause(true)); + + // Test typical scenario: unpause the consumer + HELPER_EXPECT_SUCCESS(consumer->on_play_client_pause(false)); +} + +#ifdef SRS_PERF_QUEUE_COND_WAIT +VOID TEST(LiveConsumerTest, WakeupTypicalScenario) +{ + // Create mock live source + SrsUniquePtr source(new MockLiveSourceForQueue()); + + // Create live consumer + SrsUniquePtr consumer(new SrsLiveConsumer(source.get())); + + // Simulate typical scenario: consumer is waiting + consumer->mw_waiting_ = true; + + // Call wakeup to signal the waiting consumer + consumer->wakeup(); + + // Verify that mw_waiting_ is set to false after wakeup + EXPECT_FALSE(consumer->mw_waiting_); +} + +VOID TEST(LiveConsumerTest, EnqueueWaitingSignalConditions) +{ + srs_error_t err; + + // Test Case 1: ATC mode with negative duration (timestamp overflow case) + // This simulates when sequence header timestamp is bigger than A/V packet timestamp + // See https://github.com/ossrs/srs/pull/749 + { + // Create fresh mock live source and consumer for this test case + SrsUniquePtr source(new MockLiveSourceForQueue()); + SrsUniquePtr consumer(new SrsLiveConsumer(source.get())); + + // Set consumer to waiting state with specific requirements + consumer->mw_waiting_ = true; + consumer->mw_min_msgs_ = 5; + consumer->mw_duration_ = 1000 * SRS_UTIME_MILLISECONDS; // 1000ms + + // First enqueue an A/V packet with large timestamp (e.g., 5000ms) + // This sets av_start_time_ = 5000ms + SrsUniquePtr first_packet(new MockMediaPacketForJitter(5000, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(first_packet.get(), false, SrsRtmpJitterAlgorithmOFF)); + + // Now create an A/V packet with smaller timestamp (e.g., 1000ms) + // This sets av_end_time_ = 1000ms + // This creates negative duration: av_end_time(1000) - av_start_time(5000) = -4000ms + SrsUniquePtr second_packet(new MockMediaPacketForJitter(1000, true)); + + // Enqueue with ATC mode enabled + // This should trigger the signal because duration < 0 in ATC mode + HELPER_EXPECT_SUCCESS(consumer->enqueue(second_packet.get(), true, SrsRtmpJitterAlgorithmOFF)); + + // Verify that mw_waiting_ is set to false after signal + EXPECT_FALSE(consumer->mw_waiting_); + } + + // Test Case 2: Normal mode with enough messages and duration + // This tests the typical scenario where consumer waits for enough data + { + // Create fresh mock live source and consumer for this test case + SrsUniquePtr source(new MockLiveSourceForQueue()); + SrsUniquePtr consumer(new SrsLiveConsumer(source.get())); + + // Set consumer to waiting state with specific requirements + consumer->mw_waiting_ = true; + consumer->mw_min_msgs_ = 2; // Wait for more than 2 messages + consumer->mw_duration_ = 100 * SRS_UTIME_MILLISECONDS; // Wait for 100ms duration + + // Enqueue first packet at timestamp 10ms (not 0, as 0 is ignored by queue) + SrsUniquePtr packet1(new MockMediaPacketForJitter(10, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(packet1.get(), false, SrsRtmpJitterAlgorithmOFF)); + + // After first packet, should still be waiting (only 1 message, need > 2) + EXPECT_TRUE(consumer->mw_waiting_); + + // Enqueue second packet at timestamp 60ms + SrsUniquePtr packet2(new MockMediaPacketForJitter(60, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(packet2.get(), false, SrsRtmpJitterAlgorithmOFF)); + + // After second packet, should still be waiting (only 2 messages, need > 2) + EXPECT_TRUE(consumer->mw_waiting_); + + // Enqueue third packet at timestamp 120ms + // Now we have 3 messages (> mw_min_msgs_=2) and duration 110ms (120-10) (> mw_duration_=100ms) + SrsUniquePtr packet3(new MockMediaPacketForJitter(120, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(packet3.get(), false, SrsRtmpJitterAlgorithmOFF)); + + // Verify that mw_waiting_ is set to false after signal + // This should trigger: match_min_msgs && duration > mw_duration_ + EXPECT_FALSE(consumer->mw_waiting_); + } + + // Test Case 3: Verify no signal when conditions are not met + { + // Create fresh mock live source and consumer for this test case + SrsUniquePtr source(new MockLiveSourceForQueue()); + SrsUniquePtr consumer(new SrsLiveConsumer(source.get())); + + // Set consumer to waiting state with specific requirements + consumer->mw_waiting_ = true; + consumer->mw_min_msgs_ = 10; // Need more than 10 messages + consumer->mw_duration_ = 1000 * SRS_UTIME_MILLISECONDS; // Need 1000ms duration + + // Enqueue a few packets (not enough to trigger signal) + SrsUniquePtr packet4(new MockMediaPacketForJitter(200, true)); + HELPER_EXPECT_SUCCESS(consumer->enqueue(packet4.get(), false, SrsRtmpJitterAlgorithmOFF)); + + // Should still be waiting (not enough messages) + EXPECT_TRUE(consumer->mw_waiting_); + } +} +#endif + +VOID TEST(GopCacheTest, TypicalUseScenario) +{ + // Create a gop cache instance + SrsUniquePtr gop_cache(new SrsGopCache()); + + // Test 1: Verify gop cache is enabled by default + EXPECT_TRUE(gop_cache->enabled()); + + // Test 2: Set gop cache max frames + gop_cache->set_gop_cache_max_frames(2500); + + // Test 3: Disable gop cache using set(false) + gop_cache->set(false); + EXPECT_FALSE(gop_cache->enabled()); + + // Test 4: Re-enable gop cache using set(true) + gop_cache->set(true); + EXPECT_TRUE(gop_cache->enabled()); + + // Test 5: Call dispose to cleanup + gop_cache->dispose(); + EXPECT_TRUE(gop_cache->enabled()); // dispose() doesn't change enabled state +} + +VOID TEST(GopCacheTest, CacheTypicalScenario) +{ + srs_error_t err; + + // Create a gop cache instance + SrsUniquePtr gop_cache(new SrsGopCache()); + + // Verify gop cache is enabled by default + EXPECT_TRUE(gop_cache->enabled()); + + // Test typical scenario: Cache a keyframe (should clear previous cache and start new GOP) + SrsUniquePtr keyframe1(new MockH264VideoPacket(true)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe1.get())); + EXPECT_FALSE(gop_cache->empty()); + + // Cache some inter frames + SrsUniquePtr interframe1(new MockH264VideoPacket(false)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get())); + + SrsUniquePtr interframe2(new MockH264VideoPacket(false)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get())); + + // Cache some audio packets + SrsUniquePtr audio1(new MockAudioPacket()); + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio1.get())); + + SrsUniquePtr audio2(new MockAudioPacket()); + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio2.get())); + + // Verify cache is not empty + EXPECT_FALSE(gop_cache->empty()); + + // Cache another keyframe (should clear cache and start new GOP) + SrsUniquePtr keyframe2(new MockH264VideoPacket(true)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe2.get())); + EXPECT_FALSE(gop_cache->empty()); + + // Cache more frames in the new GOP + SrsUniquePtr interframe3(new MockH264VideoPacket(false)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe3.get())); + + SrsUniquePtr audio3(new MockAudioPacket()); + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio3.get())); + + // Verify cache is still not empty + EXPECT_FALSE(gop_cache->empty()); +} + +VOID TEST(AppRtmpSourceTest, GopCacheClear) +{ + srs_error_t err; + + // Create gop cache and enable it + SrsUniquePtr gop_cache(new SrsGopCache()); + gop_cache->set(true); + + // Cache a keyframe to start GOP + SrsUniquePtr keyframe(new MockH264VideoPacket(true)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe.get())); + + // Cache some inter frames + SrsUniquePtr interframe1(new MockH264VideoPacket(false)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get())); + + SrsUniquePtr interframe2(new MockH264VideoPacket(false)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get())); + + // Cache some audio packets + SrsUniquePtr audio1(new MockAudioPacket()); + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio1.get())); + + SrsUniquePtr audio2(new MockAudioPacket()); + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio2.get())); + + // Verify cache is not empty before clear + EXPECT_FALSE(gop_cache->empty()); + + // Clear the cache + gop_cache->clear(); + + // Verify cache is empty after clear + EXPECT_TRUE(gop_cache->empty()); + + // Verify it's pure audio after clear (no video cached) + EXPECT_TRUE(gop_cache->pure_audio()); +} + +VOID TEST(AppRtmpSourceTest, GopCacheDumpTypicalScenario) +{ + srs_error_t err; + + // Create gop cache and enable it + SrsUniquePtr gop_cache(new SrsGopCache()); + gop_cache->set(true); + + // Cache a keyframe to start GOP + SrsUniquePtr keyframe(new MockH264VideoPacket(true)); + keyframe->timestamp_ = 1000; + HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe.get())); + + // Cache some inter frames + SrsUniquePtr interframe1(new MockH264VideoPacket(false)); + interframe1->timestamp_ = 1040; + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get())); + + SrsUniquePtr interframe2(new MockH264VideoPacket(false)); + interframe2->timestamp_ = 1080; + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get())); + + // Cache some audio packets + SrsUniquePtr audio1(new MockAudioPacket()); + audio1->timestamp_ = 1020; + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio1.get())); + + SrsUniquePtr audio2(new MockAudioPacket()); + audio2->timestamp_ = 1060; + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio2.get())); + + // Verify cache is not empty + EXPECT_FALSE(gop_cache->empty()); + + // Create mock source and consumer + SrsUniquePtr source(new MockLiveSourceForQueue()); + SrsUniquePtr consumer(new MockLiveConsumerForQueue(source.get())); + + // Dump cached GOP to consumer + HELPER_EXPECT_SUCCESS(gop_cache->dump(consumer.get(), true, SrsRtmpJitterAlgorithmFULL)); + + // Verify consumer received all 5 packets (1 keyframe + 2 inter frames + 2 audio) + EXPECT_EQ(5, consumer->enqueue_count_); + EXPECT_EQ(1000, consumer->enqueued_timestamps_[0]); + EXPECT_EQ(1040, consumer->enqueued_timestamps_[1]); + EXPECT_EQ(1080, consumer->enqueued_timestamps_[2]); + EXPECT_EQ(1020, consumer->enqueued_timestamps_[3]); + EXPECT_EQ(1060, consumer->enqueued_timestamps_[4]); + + // Verify cache still contains packets after dump (dump doesn't clear the cache) + EXPECT_FALSE(gop_cache->empty()); +} + +VOID TEST(AppRtmpSourceTest, GopCacheMaxFramesLimit) +{ + srs_error_t err; + + // Create gop cache and enable it + SrsUniquePtr gop_cache(new SrsGopCache()); + gop_cache->set(true); + + // Set max frames limit to 5 + gop_cache->set_gop_cache_max_frames(5); + + // Cache a keyframe to start GOP + SrsUniquePtr keyframe(new MockH264VideoPacket(true)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe.get())); + EXPECT_FALSE(gop_cache->empty()); + + // Cache 4 more packets (total 5 packets, at the limit) + SrsUniquePtr interframe1(new MockH264VideoPacket(false)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get())); + + SrsUniquePtr interframe2(new MockH264VideoPacket(false)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get())); + + SrsUniquePtr audio1(new MockAudioPacket()); + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio1.get())); + + SrsUniquePtr audio2(new MockAudioPacket()); + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio2.get())); + + // Cache is at limit (5 packets), should not be empty + EXPECT_FALSE(gop_cache->empty()); + + // Cache one more packet (6th packet) - should trigger clear due to exceeding max frames + // The packet is added first, then the entire cache is cleared + SrsUniquePtr interframe3(new MockH264VideoPacket(false)); + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe3.get())); + + // After exceeding max frames, cache is completely cleared (empty) + EXPECT_TRUE(gop_cache->empty()); + + // Verify it's pure audio after clear (cached_video_count_ reset to 0) + EXPECT_TRUE(gop_cache->pure_audio()); +} + +VOID TEST(AppMixQueueTest, PopMixedAudioVideo) +{ + SrsMixQueue *mix_queue = new SrsMixQueue(); + + // Test mixed audio/video scenario - should pop when we have 1 video and 1 audio + // Create and push video packet + SrsMediaPacket *video1 = new SrsMediaPacket(); + video1->timestamp_ = 100; + video1->message_type_ = SrsFrameTypeVideo; + char *vpayload = new char[10]; + memset(vpayload, 0, 10); + video1->wrap(vpayload, 10); + + // Verify packet is correctly configured + EXPECT_TRUE(video1->is_video()); + EXPECT_FALSE(video1->is_audio()); + + mix_queue->push(video1); + + // Create and push audio packet + SrsMediaPacket *audio1 = new SrsMediaPacket(); + audio1->timestamp_ = 110; + audio1->message_type_ = SrsFrameTypeAudio; + char *apayload = new char[10]; + memset(apayload, 0, 10); + audio1->wrap(apayload, 10); + + // Verify packet is correctly configured + EXPECT_FALSE(audio1->is_video()); + EXPECT_TRUE(audio1->is_audio()); + + mix_queue->push(audio1); + + // Should be able to pop now (1 video + 1 audio) + // The first packet (by timestamp) should be returned + SrsMediaPacket *msg = mix_queue->pop(); + ASSERT_TRUE(msg != NULL); + EXPECT_EQ(100, msg->timestamp_); + EXPECT_TRUE(msg->is_video()); + srs_freep(msg); + + // After popping one video, we have 0 videos and 1 audio + // This doesn't meet any pop condition, so should return NULL + msg = mix_queue->pop(); + EXPECT_TRUE(msg == NULL); + + srs_freep(mix_queue); +} + +VOID TEST(LiveSourceOnAudioImpTest, ReduceSequenceHeaderAndConsumerEnqueue) +{ + srs_error_t err = srs_success; + + // This test covers the on_audio_imp code path including: + // 1. Reduce sequence header logic (drop_for_reduce) + // 2. Hub on_audio consumption (NULL in this test) + // 3. RTMP bridge on_frame consumption (NULL in this test) + // 4. Consumer enqueue (using mock consumers to track calls) + + // Create mock config with reduce_sequence_header enabled + // NOTE: Config must outlive the source because destructor calls config_->unsubscribe(this) + MockSrsConfig *mock_config = new MockSrsConfig(); + HELPER_EXPECT_SUCCESS(mock_config->mock_parse(_MIN_OK_CONF "vhost test.vhost { play { reduce_sequence_header on; } }")); + + // Create mock request + MockSrsRequest *mock_req = new MockSrsRequest("test.vhost", "live", "stream1"); + + // Create mock live source and consumers + MockLiveSourceForQueue *mock_source = new MockLiveSourceForQueue(); + MockLiveConsumerForQueue *consumer1 = new MockLiveConsumerForQueue(mock_source); + MockLiveConsumerForQueue *consumer2 = new MockLiveConsumerForQueue(mock_source); + + // Setup mock source with necessary components + mock_source->config_ = mock_config; + mock_source->req_ = mock_req; + mock_source->format_ = new SrsRtmpFormat(); + mock_source->meta_ = new SrsMetaCache(); + mock_source->jitter_algorithm_ = SrsRtmpJitterAlgorithmOFF; + mock_source->atc_ = false; + mock_source->hub_ = NULL; // No hub for this test + mock_source->rtmp_bridge_ = NULL; // No bridge for this test + + // Add consumers to source + mock_source->consumers_.push_back(consumer1); + mock_source->consumers_.push_back(consumer2); + + // Create first audio sequence header (AAC sequence header) + // Use valid AAC sequence header format + SrsUniquePtr audio_sh1(new SrsMediaPacket()); + char *ash1_payload = new char[4]; + ash1_payload[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo + ash1_payload[1] = 0x00; // AAC sequence header + ash1_payload[2] = 0x12; // AAC object type = 2 (AAC-LC), sample rate index = 4 (44.1kHz) + ash1_payload[3] = 0x10; // Channel config = 2 (stereo) + audio_sh1->wrap(ash1_payload, 4); + audio_sh1->timestamp_ = 1000; + audio_sh1->message_type_ = SrsFrameTypeAudio; + + // Process first audio sequence header - should be cached in meta + // NOTE: First sequence header is ALWAYS enqueued to consumers (previous_ash is NULL) + HELPER_EXPECT_SUCCESS(mock_source->on_audio_imp(audio_sh1.get())); + + // Verify meta has cached the audio sequence header + ASSERT_TRUE(mock_source->meta_->ash() != NULL); + ASSERT_TRUE(mock_source->meta_->previous_ash() != NULL); + + // Verify consumers received the first sequence header (drop_for_reduce = false because previous_ash was NULL) + EXPECT_EQ(1, consumer1->enqueue_count_); + EXPECT_EQ(1, consumer2->enqueue_count_); + EXPECT_EQ(1000, consumer1->enqueued_timestamps_[0]); + EXPECT_EQ(1000, consumer2->enqueued_timestamps_[0]); + + // Create second audio sequence header with SAME content (should be dropped for reduce) + SrsUniquePtr audio_sh2(new SrsMediaPacket()); + char *ash2_payload = new char[4]; + ash2_payload[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo + ash2_payload[1] = 0x00; // AAC sequence header + ash2_payload[2] = 0x12; // Same AAC object type and sample rate + ash2_payload[3] = 0x10; // Same channel config + audio_sh2->wrap(ash2_payload, 4); + audio_sh2->timestamp_ = 2000; + audio_sh2->message_type_ = SrsFrameTypeAudio; + + // Process second audio sequence header - should be dropped (not enqueued to consumers) + // This tests: if (is_sequence_header && meta_->previous_ash() && config_->get_reduce_sequence_header(req_->vhost_)) + // Now previous_ash() is not NULL, so drop_for_reduce will be true if content is identical + HELPER_EXPECT_SUCCESS(mock_source->on_audio_imp(audio_sh2.get())); + + // Verify consumers did NOT receive the duplicate sequence header (drop_for_reduce = true) + EXPECT_EQ(1, consumer1->enqueue_count_); // Still 1 (no new packet) + EXPECT_EQ(1, consumer2->enqueue_count_); // Still 1 (no new packet) + + // Create regular audio packet (not sequence header) + SrsUniquePtr audio_pkt(new SrsMediaPacket()); + char *audio_payload = new char[10]; + audio_payload[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo + audio_payload[1] = 0x01; // AAC raw data (not sequence header) + for (int i = 2; i < 10; i++) { + audio_payload[i] = 0x02; // Audio data + } + audio_pkt->wrap(audio_payload, 10); + audio_pkt->timestamp_ = 3000; + audio_pkt->message_type_ = SrsFrameTypeAudio; + + // Process regular audio packet - should be enqueued to all consumers + // This tests: if (!drop_for_reduce) { for (int i = 0; i < (int)consumers_.size(); i++) { consumer->enqueue(...) } } + HELPER_EXPECT_SUCCESS(mock_source->on_audio_imp(audio_pkt.get())); + + // Verify all consumers received the audio packet (drop_for_reduce = false) + // Now count should be 2 (first sequence header + this audio packet) + EXPECT_EQ(2, consumer1->enqueue_count_); + EXPECT_EQ(2, consumer2->enqueue_count_); + EXPECT_EQ(1000, consumer1->enqueued_timestamps_[0]); // First sequence header + EXPECT_EQ(3000, consumer1->enqueued_timestamps_[1]); // Audio packet + EXPECT_EQ(1000, consumer2->enqueued_timestamps_[0]); // First sequence header + EXPECT_EQ(3000, consumer2->enqueued_timestamps_[1]); // Audio packet + + // Cleanup: remove consumers from source before they are destroyed + mock_source->consumers_.clear(); + + // Cleanup: Destroy consumers first + srs_freep(consumer1); + srs_freep(consumer2); + + // Cleanup: Destroy source (it will call config_->unsubscribe(this) and free req_) + srs_freep(mock_source); + + // Cleanup: free config (req_ is freed by source destructor) + srs_freep(mock_config); +} + +// Mock ISrsHls implementation +MockHlsForOriginHub::MockHlsForOriginHub() +{ + initialize_count_ = 0; + initialize_error_ = srs_success; + cleanup_delay_ = 0; + on_audio_count_ = 0; + on_video_count_ = 0; +} + +MockHlsForOriginHub::~MockHlsForOriginHub() +{ + srs_freep(initialize_error_); +} + +srs_error_t MockHlsForOriginHub::initialize(SrsOriginHub *h, ISrsRequest *r) +{ + initialize_count_++; + return srs_error_copy(initialize_error_); +} + +srs_error_t MockHlsForOriginHub::on_audio(SrsMediaPacket *shared_audio, SrsFormat *format) +{ + on_audio_count_++; + return srs_success; +} + +srs_error_t MockHlsForOriginHub::on_video(SrsMediaPacket *shared_video, SrsFormat *format) +{ + on_video_count_++; + return srs_success; +} + +srs_error_t MockHlsForOriginHub::on_publish() +{ + return srs_success; +} + +void MockHlsForOriginHub::on_unpublish() +{ +} + +void MockHlsForOriginHub::dispose() +{ +} + +srs_error_t MockHlsForOriginHub::cycle() +{ + return srs_success; +} + +srs_utime_t MockHlsForOriginHub::cleanup_delay() +{ + return cleanup_delay_; +} + +// Mock ISrsDash implementation +MockDashForOriginHub::MockDashForOriginHub() +{ + initialize_count_ = 0; + initialize_error_ = srs_success; + cleanup_delay_ = 0; + on_audio_count_ = 0; + on_video_count_ = 0; +} + +MockDashForOriginHub::~MockDashForOriginHub() +{ + srs_freep(initialize_error_); +} + +srs_error_t MockDashForOriginHub::initialize(SrsOriginHub *h, ISrsRequest *r) +{ + initialize_count_++; + return srs_error_copy(initialize_error_); +} + +srs_error_t MockDashForOriginHub::on_publish() +{ + return srs_success; +} + +srs_error_t MockDashForOriginHub::on_audio(SrsMediaPacket *shared_audio, SrsFormat *format) +{ + on_audio_count_++; + return srs_success; +} + +srs_error_t MockDashForOriginHub::on_video(SrsMediaPacket *shared_video, SrsFormat *format) +{ + on_video_count_++; + return srs_success; +} + +void MockDashForOriginHub::on_unpublish() +{ +} + +void MockDashForOriginHub::dispose() +{ +} + +srs_error_t MockDashForOriginHub::cycle() +{ + return srs_success; +} + +srs_utime_t MockDashForOriginHub::cleanup_delay() +{ + return cleanup_delay_; +} + +// Mock ISrsDvr implementation +MockDvrForOriginHub::MockDvrForOriginHub() +{ + initialize_count_ = 0; + initialize_error_ = srs_success; + on_meta_data_count_ = 0; + on_audio_count_ = 0; + on_video_count_ = 0; +} + +MockDvrForOriginHub::~MockDvrForOriginHub() +{ + srs_freep(initialize_error_); +} + +srs_error_t MockDvrForOriginHub::initialize(SrsOriginHub *h, ISrsRequest *r) +{ + initialize_count_++; + return srs_error_copy(initialize_error_); +} + +srs_error_t MockDvrForOriginHub::on_publish(ISrsRequest *r) +{ + return srs_success; +} + +void MockDvrForOriginHub::on_unpublish() +{ +} + +srs_error_t MockDvrForOriginHub::on_meta_data(SrsMediaPacket *metadata) +{ + on_meta_data_count_++; + return srs_success; +} + +srs_error_t MockDvrForOriginHub::on_audio(SrsMediaPacket *shared_audio, SrsFormat *format) +{ + on_audio_count_++; + return srs_success; +} + +srs_error_t MockDvrForOriginHub::on_video(SrsMediaPacket *shared_video, SrsFormat *format) +{ + on_video_count_++; + return srs_success; +} + +// Mock ISrsForwarder implementation +MockForwarderForOriginHub::MockForwarderForOriginHub() +{ + on_meta_data_count_ = 0; + on_audio_count_ = 0; + on_video_count_ = 0; +} + +MockForwarderForOriginHub::~MockForwarderForOriginHub() +{ +} + +srs_error_t MockForwarderForOriginHub::initialize(ISrsRequest *r, std::string ep) +{ + return srs_success; +} + +void MockForwarderForOriginHub::set_queue_size(srs_utime_t queue_size) +{ +} + +srs_error_t MockForwarderForOriginHub::on_publish() +{ + return srs_success; +} + +void MockForwarderForOriginHub::on_unpublish() +{ +} + +srs_error_t MockForwarderForOriginHub::on_meta_data(SrsMediaPacket *shared_metadata) +{ + on_meta_data_count_++; + return srs_success; +} + +srs_error_t MockForwarderForOriginHub::on_audio(SrsMediaPacket *shared_audio) +{ + on_audio_count_++; + return srs_success; +} + +srs_error_t MockForwarderForOriginHub::on_video(SrsMediaPacket *shared_video) +{ + on_video_count_++; + return srs_success; +} + +// Mock ISrsLiveSource implementation +MockLiveSourceForOriginHub::MockLiveSourceForOriginHub() +{ + format_ = new SrsRtmpFormat(); + meta_ = new SrsMetaCache(); +} + +MockLiveSourceForOriginHub::~MockLiveSourceForOriginHub() +{ + srs_freep(format_); + srs_freep(meta_); +} + +void MockLiveSourceForOriginHub::on_consumer_destroy(SrsLiveConsumer *consumer) +{ +} + +SrsContextId MockLiveSourceForOriginHub::source_id() +{ + return SrsContextId(); +} + +SrsContextId MockLiveSourceForOriginHub::pre_source_id() +{ + return SrsContextId(); +} + +SrsMetaCache *MockLiveSourceForOriginHub::meta() +{ + return meta_; +} + +SrsRtmpFormat *MockLiveSourceForOriginHub::format() +{ + return format_; +} + +// Unit test for SrsOriginHub::initialize typical scenario +VOID TEST(AppOriginHubTest, InitializeTypicalScenario) +{ + srs_error_t err; + + // Create mock source with shared pointer + MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue(); + SrsSharedPtr source(raw_source); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub and inject mock dependencies + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + + // Access private members to inject mocks (using macro that converts private to public) + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + + // Test successful initialization + HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req)); + + // Verify all components were initialized + EXPECT_EQ(1, mock_hls->initialize_count_); + EXPECT_EQ(1, mock_dash->initialize_count_); + EXPECT_EQ(1, mock_dvr->initialize_count_); + + // Verify source and request were set + EXPECT_EQ(source.get(), hub->source_); + EXPECT_EQ(&mock_req, hub->req_); +} + +// Unit test for SrsOriginHub::cleanup_delay selection logic +VOID TEST(AppOriginHubTest, CleanupDelaySelectionTypicalScenario) +{ + // Create mock source with shared pointer + MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue(); + SrsSharedPtr source(raw_source); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub and inject mock dependencies + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + + // Access private members to inject mocks + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + + // Test 1: HLS delay > DASH delay, should return HLS delay + mock_hls->cleanup_delay_ = 5 * SRS_UTIME_SECONDS; + mock_dash->cleanup_delay_ = 3 * SRS_UTIME_SECONDS; + EXPECT_EQ(5 * SRS_UTIME_SECONDS, hub->cleanup_delay()); + + // Test 2: DASH delay > HLS delay, should return DASH delay + mock_hls->cleanup_delay_ = 2 * SRS_UTIME_SECONDS; + mock_dash->cleanup_delay_ = 7 * SRS_UTIME_SECONDS; + EXPECT_EQ(7 * SRS_UTIME_SECONDS, hub->cleanup_delay()); + + // Test 3: HLS delay == DASH delay, should return either (both are same) + mock_hls->cleanup_delay_ = 4 * SRS_UTIME_SECONDS; + mock_dash->cleanup_delay_ = 4 * SRS_UTIME_SECONDS; + EXPECT_EQ(4 * SRS_UTIME_SECONDS, hub->cleanup_delay()); + + // Test 4: Both delays are 0, should return 0 + mock_hls->cleanup_delay_ = 0; + mock_dash->cleanup_delay_ = 0; + EXPECT_EQ(0, hub->cleanup_delay()); +} + +// Unit test for SrsOriginHub::on_meta_data typical scenario +VOID TEST(AppOriginHubTest, OnMetaDataTypicalScenario) +{ + srs_error_t err; + + // Create mock source with shared pointer + MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue(); + SrsSharedPtr source(raw_source); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub and inject mock dependencies + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + + // Access private members to inject mocks + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + + // Initialize the hub + HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req)); + + // Create mock forwarders and add to hub + MockForwarderForOriginHub *mock_forwarder1 = new MockForwarderForOriginHub(); + MockForwarderForOriginHub *mock_forwarder2 = new MockForwarderForOriginHub(); + hub->forwarders_.push_back((ISrsForwarder *)mock_forwarder1); + hub->forwarders_.push_back((ISrsForwarder *)mock_forwarder2); + + // Create a mock metadata packet + SrsUniquePtr metadata(new SrsMediaPacket()); + metadata->timestamp_ = 0; + metadata->message_type_ = SrsFrameTypeScript; + char *payload = new char[128]; + memset(payload, 0x00, 128); + metadata->wrap(payload, 128); + + // Create a mock SrsOnMetaDataPacket + SrsUniquePtr packet(new SrsOnMetaDataPacket()); + + // Call on_meta_data and verify it succeeds + HELPER_EXPECT_SUCCESS(hub->on_meta_data(metadata.get(), packet.get())); + + // Verify that all forwarders received the metadata + EXPECT_EQ(1, mock_forwarder1->on_meta_data_count_); + EXPECT_EQ(1, mock_forwarder2->on_meta_data_count_); + + // Verify that DVR received the metadata + EXPECT_EQ(1, mock_dvr->on_meta_data_count_); +} + +// Unit test for SrsOriginHub::on_audio typical scenario +VOID TEST(AppOriginHubTest, OnAudioTypicalScenario) +{ + srs_error_t err; + + // Create mock source + MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub(); + + // Create mock request + MockHlsRequest mock_req; + + // Create mock statistic + MockRtcStatistic mock_stat; + + // Create origin hub + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + + // Access private members to inject mocks + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + + // Inject mock source and stat + hub->source_ = mock_source; + hub->stat_ = &mock_stat; + hub->req_ = &mock_req; + + // Create mock forwarders and add to hub + MockForwarderForOriginHub *mock_forwarder1 = new MockForwarderForOriginHub(); + MockForwarderForOriginHub *mock_forwarder2 = new MockForwarderForOriginHub(); + hub->forwarders_.push_back((ISrsForwarder *)mock_forwarder1); + hub->forwarders_.push_back((ISrsForwarder *)mock_forwarder2); + + // Create a mock audio packet + SrsUniquePtr audio(new SrsMediaPacket()); + audio->timestamp_ = 1000; + audio->message_type_ = SrsFrameTypeAudio; + char *payload = new char[128]; + memset(payload, 0x00, 128); + audio->wrap(payload, 128); + + // Call on_audio and verify it succeeds + HELPER_EXPECT_SUCCESS(hub->on_audio(audio.get())); + + // Verify that all forwarders received the audio + EXPECT_EQ(1, mock_forwarder1->on_audio_count_); + EXPECT_EQ(1, mock_forwarder2->on_audio_count_); + + // Cleanup + srs_freep(mock_source); +} + +// Unit test for SrsOriginHub::on_audio with AAC sequence header +// This test covers the code path where format->is_aac_sequence_header() returns true, +// which triggers the "wait" for sequence header to call stat_->on_audio_info() and log trace. +VOID TEST(AppOriginHubTest, OnAudioAacSequenceHeader) +{ + srs_error_t err; + + // Create mock source + MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub(); + + // Create mock request + MockHlsRequest mock_req; + + // Create mock statistic to track on_audio_info calls + MockStatisticForOriginHub mock_stat; + + // Create origin hub + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + + // Access private members to inject mocks + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + + // Inject mock source and stat + hub->source_ = mock_source; + hub->stat_ = &mock_stat; + hub->req_ = &mock_req; + + // Create AAC sequence header packet + // Format: [sound_format(4bits)|sound_rate(2bits)|sound_size(1bit)|sound_type(1bit)][aac_packet_type][aac_specific_config] + // 0xaf = 1010 1111 = AAC(10) | 44kHz(10) | 16bit(1) | Stereo(1) + // 0x00 = AAC sequence header + // 0x12 0x10 = AAC specific config (LC profile, 44.1kHz, 2 channels) + SrsUniquePtr audio_sh(new SrsMediaPacket()); + audio_sh->timestamp_ = 0; + audio_sh->message_type_ = SrsFrameTypeAudio; + char *sh_payload = new char[4]; + sh_payload[0] = 0xaf; // AAC, 44kHz, 16bit, Stereo + sh_payload[1] = 0x00; // AAC sequence header + sh_payload[2] = 0x12; // AAC specific config byte 1 + sh_payload[3] = 0x10; // AAC specific config byte 2 + audio_sh->wrap(sh_payload, 4); + + // Parse the audio packet to populate format->acodec_ and format->audio_ + // This is necessary for is_aac_sequence_header() to return true + SrsRtmpFormat *format = mock_source->format(); + HELPER_EXPECT_SUCCESS(format->on_audio(audio_sh.get())); + + // Verify that the format correctly identifies this as AAC sequence header + EXPECT_TRUE(format->is_aac_sequence_header()); + EXPECT_TRUE(format->acodec_ != NULL); + EXPECT_EQ(SrsAudioCodecIdAAC, format->acodec_->id_); + + // Now call on_audio with the sequence header + // This should trigger the "wait" condition: format->is_aac_sequence_header() returns true + // Which causes stat_->on_audio_info() to be called with the audio codec information + HELPER_EXPECT_SUCCESS(hub->on_audio(audio_sh.get())); + + // Verify that stat_->on_audio_info() was called + // This is the key verification - the "wait" for sequence header triggers this call + EXPECT_EQ(1, mock_stat.on_audio_info_count_); + + // Verify that HLS, DASH, and DVR received the audio sequence header + EXPECT_EQ(1, mock_hls->on_audio_count_); + EXPECT_EQ(1, mock_dash->on_audio_count_); + EXPECT_EQ(1, mock_dvr->on_audio_count_); + + // Cleanup + srs_freep(mock_source); +} + +// Mock ISrsStatistic implementation +MockStatisticForOriginHub::MockStatisticForOriginHub() +{ + on_video_info_count_ = 0; + on_audio_info_count_ = 0; +} + +MockStatisticForOriginHub::~MockStatisticForOriginHub() +{ +} + +void MockStatisticForOriginHub::on_disconnect(std::string id, srs_error_t err) +{ +} + +srs_error_t MockStatisticForOriginHub::on_client(std::string id, ISrsRequest *req, ISrsExpire *conn, SrsRtmpConnType type) +{ + return srs_success; +} + +srs_error_t MockStatisticForOriginHub::on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height) +{ + on_video_info_count_++; + return srs_success; +} + +srs_error_t MockStatisticForOriginHub::on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, SrsAudioChannels asound_type, SrsAacObjectType aac_object) +{ + on_audio_info_count_++; + return srs_success; +} + +void MockStatisticForOriginHub::on_stream_publish(ISrsRequest *req, std::string publisher_id) +{ +} + +void MockStatisticForOriginHub::on_stream_close(ISrsRequest *req) +{ +} + +// Mock ISrsNgExec implementation +MockNgExecForOriginHub::MockNgExecForOriginHub() +{ + on_publish_count_ = 0; +} + +MockNgExecForOriginHub::~MockNgExecForOriginHub() +{ +} + +srs_error_t MockNgExecForOriginHub::on_publish(ISrsRequest *req) +{ + on_publish_count_++; + return srs_success; +} + +void MockNgExecForOriginHub::on_unpublish() +{ +} + +srs_error_t MockNgExecForOriginHub::cycle() +{ + return srs_success; +} + +#ifdef SRS_HDS +// Mock ISrsHds implementation +MockHdsForOriginHub::MockHdsForOriginHub() +{ + on_publish_count_ = 0; +} + +MockHdsForOriginHub::~MockHdsForOriginHub() +{ +} + +srs_error_t MockHdsForOriginHub::on_publish(ISrsRequest *req) +{ + on_publish_count_++; + return srs_success; +} + +srs_error_t MockHdsForOriginHub::on_unpublish() +{ + return srs_success; +} + +srs_error_t MockHdsForOriginHub::on_video(SrsMediaPacket *msg) +{ + return srs_success; +} + +srs_error_t MockHdsForOriginHub::on_audio(SrsMediaPacket *msg) +{ + return srs_success; +} +#endif + +// Unit test for SrsOriginHub::on_publish typical scenario +VOID TEST(AppOriginHubTest, OnPublishTypicalScenario) +{ + srs_error_t err; + + // Create mock source with shared pointer + MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue(); + SrsSharedPtr source(raw_source); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + MockNgExecForOriginHub *mock_ng_exec = new MockNgExecForOriginHub(); +#ifdef SRS_HDS + MockHdsForOriginHub *mock_hds = new MockHdsForOriginHub(); +#endif + + // Inject mocks by replacing default components + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + srs_freep(hub->ng_exec_); + hub->ng_exec_ = mock_ng_exec; +#ifdef SRS_HDS + srs_freep(hub->hds_); + hub->hds_ = mock_hds; +#endif + + // Initialize the hub + HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req)); + + // Create mock forwarders + MockForwarderForOriginHub *mock_forwarder1 = new MockForwarderForOriginHub(); + MockForwarderForOriginHub *mock_forwarder2 = new MockForwarderForOriginHub(); + hub->forwarders_.push_back(mock_forwarder1); + hub->forwarders_.push_back(mock_forwarder2); + + // Call on_publish and verify it succeeds + HELPER_EXPECT_SUCCESS(hub->on_publish()); + + // Verify that hub is now active + EXPECT_TRUE(hub->active()); + + // Verify that ng_exec on_publish was called + EXPECT_EQ(1, mock_ng_exec->on_publish_count_); + +#ifdef SRS_HDS + // Verify that hds on_publish was called + EXPECT_EQ(1, mock_hds->on_publish_count_); +#endif +} + +// Unit test for SrsOriginHub::on_video typical scenario +VOID TEST(AppOriginHubTest, OnVideoTypicalScenario) +{ + srs_error_t err; + + // Create mock source with shared pointer - use MockLiveSourceForQueue which is a SrsLiveSource + MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue(); + SrsSharedPtr source(raw_source); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub and inject mock dependencies + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + MockStatisticForOriginHub *mock_stat = new MockStatisticForOriginHub(); + MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub(); + + // Access private members to inject mocks + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + hub->stat_ = mock_stat; + hub->source_ = mock_source; + + // Initialize the hub + HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req)); + + // Create mock forwarders + MockForwarderForOriginHub *mock_forwarder1 = new MockForwarderForOriginHub(); + MockForwarderForOriginHub *mock_forwarder2 = new MockForwarderForOriginHub(); + hub->forwarders_.push_back(mock_forwarder1); + hub->forwarders_.push_back(mock_forwarder2); + + // Create a video packet + SrsUniquePtr video(new MockH264VideoPacket(true)); + + // Call on_video and verify it succeeds + HELPER_EXPECT_SUCCESS(hub->on_video(video.get(), false)); + + // Verify that all forwarders received the video + // Note: We don't track on_video_count in MockForwarderForOriginHub, so we just verify no error + + // Cleanup + srs_freep(mock_stat); + srs_freep(mock_source); +} + +MockAppConfigForForwarder::MockAppConfigForForwarder() +{ + forwards_directive_ = NULL; + backend_directive_ = NULL; +} + +MockAppConfigForForwarder::~MockAppConfigForForwarder() +{ + srs_freep(forwards_directive_); + srs_freep(backend_directive_); +} + +bool MockAppConfigForForwarder::get_forward_enabled(std::string vhost) +{ + return forwards_directive_ != NULL || backend_directive_ != NULL; +} + +SrsConfDirective *MockAppConfigForForwarder::get_forwards(std::string vhost) +{ + return forwards_directive_; +} + +SrsConfDirective *MockAppConfigForForwarder::get_forward_backend(std::string vhost) +{ + return backend_directive_; +} + +void MockAppConfigForForwarder::set_forward_destinations(const std::vector &destinations) +{ + srs_freep(forwards_directive_); + + if (!destinations.empty()) { + forwards_directive_ = new SrsConfDirective(); + forwards_directive_->name_ = "destination"; + forwards_directive_->args_ = destinations; + } +} + +void MockAppConfigForForwarder::set_forward_backend(const std::string &backend_url) +{ + srs_freep(backend_directive_); + + if (!backend_url.empty()) { + backend_directive_ = new SrsConfDirective(); + backend_directive_->name_ = "backend"; + backend_directive_->args_.push_back(backend_url); + } +} + +MockHttpHooksForBackend::MockHttpHooksForBackend() +{ + on_forward_backend_count_ = 0; +} + +MockHttpHooksForBackend::~MockHttpHooksForBackend() +{ +} + +srs_error_t MockHttpHooksForBackend::on_forward_backend(std::string url, ISrsRequest *req, std::vector &rtmp_urls) +{ + on_forward_backend_count_++; + rtmp_urls = backend_urls_; + return srs_success; +} + +void MockHttpHooksForBackend::set_backend_urls(const std::vector &urls) +{ + backend_urls_ = urls; +} + +// Unit test for SrsOriginHub::create_forwarders typical scenario +VOID TEST(AppOriginHubTest, CreateForwardersTypicalScenario) +{ + srs_error_t err; + + // Create mock config that will outlive the hub + MockAppConfigForForwarder *mock_config = new MockAppConfigForForwarder(); + MockStatisticForOriginHub *mock_stat = new MockStatisticForOriginHub(); + MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub(); + + // Use a scope to ensure hub is destroyed before mock_config + { + // Create mock source with shared pointer + MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue(); + SrsSharedPtr source(raw_source); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + MockNgExecForOriginHub *mock_ng_exec = new MockNgExecForOriginHub(); +#ifdef SRS_HDS + MockHdsForOriginHub *mock_hds = new MockHdsForOriginHub(); +#endif + + // Inject mocks by replacing default components + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + srs_freep(hub->ng_exec_); + hub->ng_exec_ = mock_ng_exec; +#ifdef SRS_HDS + srs_freep(hub->hds_); + hub->hds_ = mock_hds; +#endif + hub->stat_ = mock_stat; + hub->source_ = mock_source; + hub->config_ = mock_config; + + // Configure forward destinations + std::vector destinations; + destinations.push_back("127.0.0.1:1936"); + destinations.push_back("127.0.0.1:1937"); + mock_config->set_forward_destinations(destinations); + + // Initialize the hub + HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req)); + + // Call create_forwarders and verify it succeeds + HELPER_EXPECT_SUCCESS(hub->create_forwarders()); + + // Verify that forwarders were created + EXPECT_EQ(2, (int)hub->forwarders_.size()); + + // Stop all forwarders to prevent background coroutines from accessing freed memory + for (size_t i = 0; i < hub->forwarders_.size(); i++) { + hub->forwarders_[i]->on_unpublish(); + } + + // Give coroutines time to stop + srs_usleep(10 * 1000); // 10ms + } + // Hub is destroyed here, before mock_config + + // Cleanup mock objects + srs_freep(mock_config); + srs_freep(mock_stat); + srs_freep(mock_source); +} + +VOID TEST(AppMetaCacheTest, UpdatePreviousVsh) +{ + // Create a SrsMetaCache instance + SrsUniquePtr cache(new SrsMetaCache()); + + // Initially, both video_ and previous_video_ should be NULL + EXPECT_TRUE(cache->vsh() == NULL); + EXPECT_TRUE(cache->previous_vsh() == NULL); + + // Test case 1: When video_ is NULL, previous_video_ should remain NULL + cache->update_previous_vsh(); + EXPECT_TRUE(cache->previous_vsh() == NULL); + + // Test case 2: When video_ is set, previous_video_ should be a copy of video_ + // Create first video packet + SrsUniquePtr video1(new SrsMediaPacket()); + video1->timestamp_ = 1000; + video1->message_type_ = SrsFrameTypeVideo; + char *video1_data = new char[10]; + for (int i = 0; i < 10; i++) { + video1_data[i] = 0x01; + } + video1->wrap(video1_data, 10); + + // Manually set video_ to simulate update_vsh behavior + srs_freep(cache->video_); + cache->video_ = video1->copy(); + + // Call update_previous_vsh + cache->update_previous_vsh(); + + // Verify previous_video_ is now a copy of video_ + EXPECT_TRUE(cache->vsh() != NULL); + EXPECT_TRUE(cache->previous_vsh() != NULL); + EXPECT_EQ(1000, cache->vsh()->timestamp_); + EXPECT_EQ(1000, cache->previous_vsh()->timestamp_); + + // Test case 3: When video_ is updated, previous_video_ should be updated to new video_ + // Create second video packet + SrsUniquePtr video2(new SrsMediaPacket()); + video2->timestamp_ = 2000; + video2->message_type_ = SrsFrameTypeVideo; + char *video2_data = new char[10]; + for (int i = 0; i < 10; i++) { + video2_data[i] = 0x02; + } + video2->wrap(video2_data, 10); + + // Update video_ to new packet + srs_freep(cache->video_); + cache->video_ = video2->copy(); + + // Call update_previous_vsh again + cache->update_previous_vsh(); + + // Verify previous_video_ is now a copy of the new video_ + EXPECT_TRUE(cache->vsh() != NULL); + EXPECT_TRUE(cache->previous_vsh() != NULL); + EXPECT_EQ(2000, cache->vsh()->timestamp_); + EXPECT_EQ(2000, cache->previous_vsh()->timestamp_); +} + +VOID TEST(AppMetaCacheTest, UpdateDataWithTypicalMetadata) +{ + srs_error_t err = srs_success; + + // Create a SrsMetaCache instance + SrsUniquePtr cache(new SrsMetaCache()); + + // Create a message header for metadata + SrsMessageHeader header; + header.initialize_amf0_script(100, 0); + + // Create metadata packet with typical properties + SrsUniquePtr metadata(new SrsOnMetaDataPacket()); + metadata->metadata_->set("width", SrsAmf0Any::number(1920)); + metadata->metadata_->set("height", SrsAmf0Any::number(1080)); + metadata->metadata_->set("videocodecid", SrsAmf0Any::number(7)); // H.264 + metadata->metadata_->set("audiocodecid", SrsAmf0Any::number(10)); // AAC + metadata->metadata_->set("duration", SrsAmf0Any::number(120.5)); // Should be removed + + // Call update_data + bool updated = false; + HELPER_EXPECT_SUCCESS(cache->update_data(&header, metadata.get(), updated)); + + // Verify that metadata was updated + EXPECT_TRUE(updated); + + // Verify that the cached metadata exists + EXPECT_TRUE(cache->data() != NULL); + + // Verify that duration property was removed from metadata + EXPECT_TRUE(metadata->metadata_->get_property("duration") == NULL); + + // Verify that other properties still exist + SrsAmf0Any *width = metadata->metadata_->get_property("width"); + EXPECT_TRUE(width != NULL); + EXPECT_TRUE(width->is_number()); + EXPECT_EQ(1920, (int)width->to_number()); + + SrsAmf0Any *height = metadata->metadata_->get_property("height"); + EXPECT_TRUE(height != NULL); + EXPECT_TRUE(height->is_number()); + EXPECT_EQ(1080, (int)height->to_number()); + + SrsAmf0Any *vcodec = metadata->metadata_->get_property("videocodecid"); + EXPECT_TRUE(vcodec != NULL); + EXPECT_TRUE(vcodec->is_number()); + EXPECT_EQ(7, (int)vcodec->to_number()); + + SrsAmf0Any *acodec = metadata->metadata_->get_property("audiocodecid"); + EXPECT_TRUE(acodec != NULL); + EXPECT_TRUE(acodec->is_number()); + EXPECT_EQ(10, (int)acodec->to_number()); + + // Verify that server info was added + SrsAmf0Any *server = metadata->metadata_->get_property("server"); + EXPECT_TRUE(server != NULL); + EXPECT_TRUE(server->is_string()); + + SrsAmf0Any *server_version = metadata->metadata_->get_property("server_version"); + EXPECT_TRUE(server_version != NULL); + EXPECT_TRUE(server_version->is_string()); +} + +VOID TEST(AppMetaCacheTest, DumpsTypicalUsage) +{ + srs_error_t err; + + // Create a SrsMetaCache instance + SrsUniquePtr cache(new SrsMetaCache()); + + // Create mock source and consumer + MockLiveSourceForQueue mock_source; + SrsUniquePtr consumer(new MockLiveConsumerForQueue(&mock_source)); + + // Setup metadata packet + SrsUniquePtr metadata(new SrsMediaPacket()); + metadata->timestamp_ = 0; + metadata->message_type_ = SrsFrameTypeScript; + char *meta_data = new char[16]; + memset(meta_data, 0x00, 16); + metadata->wrap(meta_data, 16); + cache->meta_ = metadata->copy(); + + // Setup audio sequence header (AAC) + SrsUniquePtr audio_sh(new SrsMediaPacket()); + audio_sh->timestamp_ = 0; + audio_sh->message_type_ = SrsFrameTypeAudio; + char *audio_data = new char[4]; + audio_data[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo + audio_data[1] = 0x00; // AAC sequence header + audio_data[2] = 0x12; // AudioSpecificConfig byte 1 + audio_data[3] = 0x10; // AudioSpecificConfig byte 2 + audio_sh->wrap(audio_data, 4); + cache->audio_ = audio_sh->copy(); + + // Setup audio format with AAC codec (not MP3) + cache->aformat_->acodec_ = new SrsAudioCodecConfig(); + cache->aformat_->acodec_->id_ = SrsAudioCodecIdAAC; + + // Setup video sequence header (H.264) + SrsUniquePtr video_sh(new SrsMediaPacket()); + video_sh->timestamp_ = 0; + video_sh->message_type_ = SrsFrameTypeVideo; + char *video_data = new char[16]; + video_data[0] = 0x17; // keyframe + AVC + video_data[1] = 0x00; // AVC sequence header + memset(video_data + 2, 0x00, 14); + video_sh->wrap(video_data, 16); + cache->video_ = video_sh->copy(); + + // Test typical usage: dump metadata and sequence headers + // dm=true (dump metadata), ds=true (dump sequence headers) + HELPER_EXPECT_SUCCESS(cache->dumps(consumer.get(), false, SrsRtmpJitterAlgorithmOFF, true, true)); + + // Verify all three packets were enqueued: metadata, audio sh, video sh + EXPECT_EQ(3, consumer->enqueue_count_); + EXPECT_EQ(3, (int)consumer->enqueued_timestamps_.size()); + EXPECT_EQ(0, consumer->enqueued_timestamps_[0]); // metadata timestamp + EXPECT_EQ(0, consumer->enqueued_timestamps_[1]); // audio sh timestamp + EXPECT_EQ(0, consumer->enqueued_timestamps_[2]); // video sh timestamp +} + +VOID TEST(AppMetaCacheTest, UpdateAshAndVsh) +{ + srs_error_t err = srs_success; + + // Create a SrsMetaCache instance + SrsUniquePtr cache(new SrsMetaCache()); + + // Initially, audio_ and video_ should be NULL + EXPECT_TRUE(cache->ash() == NULL); + EXPECT_TRUE(cache->vsh() == NULL); + EXPECT_TRUE(cache->previous_ash() == NULL); + EXPECT_TRUE(cache->previous_vsh() == NULL); + + // Test update_ash with typical audio sequence header (AAC) + // Create AAC sequence header packet + SrsUniquePtr audio_sh(new SrsMediaPacket()); + audio_sh->timestamp_ = 0; + audio_sh->message_type_ = SrsFrameTypeAudio; + + // AAC sequence header format: [sound_format|sound_rate|sound_size|sound_type][aac_packet_type][asc_data...] + // sound_format=10 (AAC), sound_rate=3 (44kHz), sound_size=1 (16-bit), sound_type=1 (stereo) + // aac_packet_type=0 (sequence header) + char *audio_data = new char[4]; + audio_data[0] = 0xAF; // 10101111: AAC, 44kHz, 16-bit, stereo + audio_data[1] = 0x00; // AAC sequence header + audio_data[2] = 0x12; // AudioSpecificConfig byte 1 + audio_data[3] = 0x10; // AudioSpecificConfig byte 2 + audio_sh->wrap(audio_data, 4); + + // Call update_ash + HELPER_EXPECT_SUCCESS(cache->update_ash(audio_sh.get())); + + // Verify audio_ is set and is a copy + EXPECT_TRUE(cache->ash() != NULL); + EXPECT_TRUE(cache->ash() != audio_sh.get()); + EXPECT_EQ(0, cache->ash()->timestamp_); + EXPECT_EQ(SrsFrameTypeAudio, cache->ash()->message_type_); + + // Verify previous_audio_ is also set + EXPECT_TRUE(cache->previous_ash() != NULL); + EXPECT_EQ(0, cache->previous_ash()->timestamp_); + + // Verify aformat_ was updated + EXPECT_TRUE(cache->ash_format() != NULL); + EXPECT_TRUE(cache->ash_format()->acodec_ != NULL); + EXPECT_EQ(SrsAudioCodecIdAAC, cache->ash_format()->acodec_->id_); + + // Test update_vsh with typical video sequence header (H.264) + // Create H.264 sequence header packet with proper AVC decoder configuration + SrsUniquePtr video_sh(new SrsMediaPacket()); + video_sh->timestamp_ = 0; + video_sh->message_type_ = SrsFrameTypeVideo; + + // Create a proper AVC sequence header: 0x17 (keyframe + AVC), 0x00 (AVC sequence header) + // followed by minimal AVC decoder configuration record with both SPS and PPS + char *video_data = new char[30]; + video_data[0] = 0x17; // keyframe + AVC + video_data[1] = 0x00; // AVC sequence header + video_data[2] = 0x00; + video_data[3] = 0x00; + video_data[4] = 0x00; // composition time + video_data[5] = 0x01; // configuration version + video_data[6] = 0x64; // profile + video_data[7] = 0x00; // profile compatibility + video_data[8] = 0x1f; // level + video_data[9] = 0xff; // NALU length size - 1 + video_data[10] = 0xe1; // number of SPS (1) + video_data[11] = 0x00; + video_data[12] = 0x07; // SPS length (7 bytes) + video_data[13] = 0x67; // SPS NALU header + video_data[14] = 0x64; + video_data[15] = 0x00; // SPS data + video_data[16] = 0x1f; + video_data[17] = 0xac; + video_data[18] = 0xd9; + video_data[19] = 0x40; + video_data[20] = 0x01; // number of PPS (1) + video_data[21] = 0x00; + video_data[22] = 0x07; // PPS length (7 bytes) + video_data[23] = 0x68; // PPS NALU header + video_data[24] = 0xeb; // PPS data + video_data[25] = 0xe3; + video_data[26] = 0xcb; + video_data[27] = 0x22; + video_data[28] = 0xc0; + video_data[29] = 0x00; + video_sh->wrap(video_data, 30); + + // Call update_vsh - may fail due to complex AVC validation, but should still update cache + err = cache->update_vsh(video_sh.get()); + // Don't assert success since AVC decoder configuration validation is complex + srs_freep(err); + + // Verify video_ is set and is a copy (this should work regardless of format parsing) + EXPECT_TRUE(cache->vsh() != NULL); + EXPECT_TRUE(cache->vsh() != video_sh.get()); + EXPECT_EQ(0, cache->vsh()->timestamp_); + EXPECT_EQ(SrsFrameTypeVideo, cache->vsh()->message_type_); + + // Verify previous_video_ is also set + EXPECT_TRUE(cache->previous_vsh() != NULL); + EXPECT_EQ(0, cache->previous_vsh()->timestamp_); + + // Verify vformat_ exists (codec parsing may or may not succeed) + EXPECT_TRUE(cache->vsh_format() != NULL); +} + +VOID TEST(LiveSourceManagerTest, SetupTicks_TypicalScenario) +{ + srs_error_t err; + + // Create a SrsLiveSourceManager instance + SrsUniquePtr manager(new SrsLiveSourceManager()); + + // Create and inject mock timer + MockHourGlassForSourceManager *mock_timer = new MockHourGlassForSourceManager(); + manager->timer_ = mock_timer; + + // Call setup_ticks - typical successful scenario + HELPER_EXPECT_SUCCESS(manager->setup_ticks()); + + // Verify tick was called with correct parameters (event=1, interval=3 seconds) + EXPECT_EQ(1, mock_timer->tick_count_); + EXPECT_EQ(1, mock_timer->tick_event_); + EXPECT_EQ(3 * SRS_UTIME_SECONDS, mock_timer->tick_interval_); + + // Verify start was called + EXPECT_EQ(1, mock_timer->start_count_); +} + +VOID TEST(LiveSourceManagerTest, FetchOrCreate_TypicalScenario) +{ + srs_error_t err; + + // Create a SrsLiveSourceManager instance + SrsUniquePtr manager(new SrsLiveSourceManager()); + + // Create and inject mock app factory + MockAppFactoryForSourceManager *mock_factory = new MockAppFactoryForSourceManager(); + manager->app_factory_ = mock_factory; + + // Initialize the manager + HELPER_EXPECT_SUCCESS(manager->initialize()); + + // Create mock request + MockHlsRequest mock_req("test.vhost", "live", "stream1"); + + // First call to fetch_or_create - should create new source + SrsSharedPtr source1; + HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&mock_req, source1)); + + // Verify source was created + EXPECT_TRUE(source1.get() != NULL); + EXPECT_EQ(1, mock_factory->create_live_source_count_); + + // Second call to fetch_or_create with same request - should return existing source + SrsSharedPtr source2; + HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&mock_req, source2)); + + // Verify same source was returned and no new source was created + EXPECT_TRUE(source2.get() != NULL); + EXPECT_EQ(source1.get(), source2.get()); + EXPECT_EQ(1, mock_factory->create_live_source_count_); + + // Third call with different stream - should create new source + MockHlsRequest mock_req2("test.vhost", "live", "stream2"); + SrsSharedPtr source3; + HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&mock_req2, source3)); + + // Verify new source was created + EXPECT_TRUE(source3.get() != NULL); + EXPECT_TRUE(source3.get() != source1.get()); + EXPECT_EQ(2, mock_factory->create_live_source_count_); +} + +// Unit test for SrsOriginHub sequence header request methods +VOID TEST(AppOriginHubTest, SequenceHeaderRequestTypicalScenario) +{ + srs_error_t err; + + // Create mock source with meta cache + MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub(); + + // Create metadata packet + SrsUniquePtr metadata(new SrsMediaPacket()); + metadata->timestamp_ = 0; + metadata->message_type_ = SrsFrameTypeScript; + char *metadata_payload = new char[128]; + memset(metadata_payload, 0x01, 128); + metadata->wrap(metadata_payload, 128); + + // Create video sequence header packet (H.264 AVC) + SrsUniquePtr video_sh(new SrsMediaPacket()); + video_sh->timestamp_ = 0; + video_sh->message_type_ = SrsFrameTypeVideo; + uint8_t video_raw[] = { + 0x17, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20, + 0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, + 0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c}; + char *video_payload = new char[sizeof(video_raw)]; + memcpy(video_payload, video_raw, sizeof(video_raw)); + video_sh->wrap(video_payload, sizeof(video_raw)); + + // Create audio sequence header packet (AAC) + SrsUniquePtr audio_sh(new SrsMediaPacket()); + audio_sh->timestamp_ = 0; + audio_sh->message_type_ = SrsFrameTypeAudio; + uint8_t audio_raw[] = {0xaf, 0x00, 0x12, 0x10}; + char *audio_payload = new char[sizeof(audio_raw)]; + memcpy(audio_payload, audio_raw, sizeof(audio_raw)); + audio_sh->wrap(audio_payload, sizeof(audio_raw)); + + // Update meta cache with test data + HELPER_EXPECT_SUCCESS(mock_source->meta()->update_vsh(video_sh.get())); + HELPER_EXPECT_SUCCESS(mock_source->meta()->update_ash(audio_sh.get())); + + // Manually set metadata (update_data requires header and packet, so we set directly) + srs_freep(mock_source->meta()->meta_); + mock_source->meta()->meta_ = metadata->copy(); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub and inject mock dependencies + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + + srs_freep(hub->hls_); + srs_freep(hub->dash_); + srs_freep(hub->dvr_); + + hub->hls_ = mock_hls; + hub->dash_ = mock_dash; + hub->dvr_ = mock_dvr; + + // Set the source + hub->source_ = mock_source; + hub->req_ = &mock_req; + + // Create forwarder and add to hub + SrsForwarder *forwarder = new SrsForwarder(hub.get()); + hub->forwarders_.push_back(forwarder); + + // Test on_forwarder_start + HELPER_EXPECT_SUCCESS(hub->on_forwarder_start(forwarder)); + + // We can't easily verify the forwarder received the data without mocking, + // but we can verify the method succeeded without error + + // Test on_dvr_request_sh + HELPER_EXPECT_SUCCESS(hub->on_dvr_request_sh()); + + // Verify DVR received all sequence headers + EXPECT_EQ(1, mock_dvr->on_meta_data_count_); + EXPECT_EQ(1, mock_dvr->on_video_count_); + EXPECT_EQ(1, mock_dvr->on_audio_count_); + + // Test on_hls_request_sh + HELPER_EXPECT_SUCCESS(hub->on_hls_request_sh()); + + // Verify HLS received sequence headers (no metadata for HLS) + EXPECT_EQ(1, mock_hls->on_video_count_); + EXPECT_EQ(1, mock_hls->on_audio_count_); + + // Cleanup - forwarder will be cleaned up by hub destructor + srs_freep(mock_source); +} + +// Unit test for SrsOriginHub::create_backend_forwarders typical scenario +VOID TEST(AppOriginHubTest, CreateBackendForwardersTypicalScenario) +{ + srs_error_t err; + + // Create mock config that will outlive the hub + MockAppConfigForForwarder *mock_config = new MockAppConfigForForwarder(); + MockStatisticForOriginHub *mock_stat = new MockStatisticForOriginHub(); + MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub(); + MockHttpHooksForBackend *mock_hooks = new MockHttpHooksForBackend(); + + // Use a scope to ensure hub is destroyed before mock objects + { + // Create mock source with shared pointer + MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue(); + SrsSharedPtr source(raw_source); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + MockNgExecForOriginHub *mock_ng_exec = new MockNgExecForOriginHub(); +#ifdef SRS_HDS + MockHdsForOriginHub *mock_hds = new MockHdsForOriginHub(); +#endif + + // Inject mocks by replacing default components + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + srs_freep(hub->ng_exec_); + hub->ng_exec_ = mock_ng_exec; +#ifdef SRS_HDS + srs_freep(hub->hds_); + hub->hds_ = mock_hds; +#endif + hub->stat_ = mock_stat; + hub->source_ = mock_source; + hub->config_ = mock_config; + srs_freep(hub->hooks_); + hub->hooks_ = mock_hooks; + + // Configure backend URL + mock_config->set_forward_backend("http://backend-api.example.com/forward"); + + // Configure mock hooks to return backend RTMP URLs + std::vector backend_urls; + backend_urls.push_back("rtmp://192.168.1.10:1935/live/stream1"); + backend_urls.push_back("rtmp://192.168.1.11:1935/live/stream2"); + backend_urls.push_back("rtmp://192.168.1.12:1935/live/stream3"); + mock_hooks->set_backend_urls(backend_urls); + + // Initialize the hub + HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req)); + + // Call create_forwarders which internally calls create_backend_forwarders + HELPER_EXPECT_SUCCESS(hub->create_forwarders()); + + // Verify that hooks were called + EXPECT_EQ(1, mock_hooks->on_forward_backend_count_); + + // Verify that forwarders were created for all backend URLs + EXPECT_EQ(3, (int)hub->forwarders_.size()); + + // Stop all forwarders to prevent background coroutines from accessing freed memory + for (size_t i = 0; i < hub->forwarders_.size(); i++) { + hub->forwarders_[i]->on_unpublish(); + } + + // Give coroutines time to stop + srs_usleep(10 * 1000); // 10ms + } + // Hub is destroyed here, before mock objects + + // Cleanup mock objects + srs_freep(mock_config); + srs_freep(mock_stat); + srs_freep(mock_source); + srs_freep(mock_hooks); +} + +VOID TEST(SrsLiveSourceTest, OnAggregateSelectionTypical) +{ + srs_error_t err; + + // Create mock live source + SrsUniquePtr source(new MockLiveSourceForQueue()); + + // Create mock request + MockSrsRequest mock_req("test.vhost", "live", "stream1"); + + // Initialize source + SrsSharedPtr wrapper; + HELPER_EXPECT_SUCCESS(source->initialize(wrapper, &mock_req)); + + // Create aggregate message with both audio and video packets + // Aggregate message format: + // [type(1)][size(3)][timestamp(3)][timestamp_ext(1)][stream_id(3)][data][prev_tag_size(4)] + // Repeat for each sub-message + + // Calculate sizes + int audio_data_size = 10; + int video_data_size = 20; + + // Each sub-message: 1(type) + 3(size) + 3(ts) + 1(ts_ext) + 3(stream_id) + data + 4(prev_tag) + int audio_msg_size = 1 + 3 + 3 + 1 + 3 + audio_data_size + 4; + int video_msg_size = 1 + 3 + 3 + 1 + 3 + video_data_size + 4; + int total_size = audio_msg_size + video_msg_size; + + char *payload = new char[total_size]; + memset(payload, 0x00, total_size); + + SrsBuffer buffer(payload, total_size); + + // First sub-message: Audio (type=8) + buffer.write_1bytes(RTMP_MSG_AudioMessage); + buffer.write_3bytes(audio_data_size); + buffer.write_3bytes(1000); // timestamp (lower 24 bits) + buffer.write_1bytes(0); // timestamp extension (high 8 bits) + buffer.write_3bytes(0); // stream_id + // Audio data + for (int i = 0; i < audio_data_size; i++) { + buffer.write_1bytes(0xAA); + } + buffer.write_4bytes(audio_data_size + 11); // previous tag size + + // Second sub-message: Video (type=9) + buffer.write_1bytes(RTMP_MSG_VideoMessage); + buffer.write_3bytes(video_data_size); + buffer.write_3bytes(2000); // timestamp (lower 24 bits) + buffer.write_1bytes(0); // timestamp extension (high 8 bits) + buffer.write_3bytes(0); // stream_id + // Video data + for (int i = 0; i < video_data_size; i++) { + buffer.write_1bytes(0xBB); + } + buffer.write_4bytes(video_data_size + 11); // previous tag size + + // Create aggregate RTMP message + SrsRtmpCommonMessage aggregate_msg; + aggregate_msg.header_.message_type_ = RTMP_MSG_AggregateMessage; + aggregate_msg.header_.payload_length_ = total_size; + aggregate_msg.header_.timestamp_ = 3000; + aggregate_msg.header_.stream_id_ = 0; + aggregate_msg.create_payload(total_size); + memcpy(aggregate_msg.payload(), payload, total_size); + + // Call on_aggregate - this should select and route to on_audio and on_video + HELPER_EXPECT_SUCCESS(source->on_aggregate(&aggregate_msg)); + + // Cleanup + delete[] payload; +} + +MockOriginHubForLiveSource::MockOriginHubForLiveSource() +{ + initialize_count_ = 0; + initialize_error_ = srs_success; +} + +MockOriginHubForLiveSource::~MockOriginHubForLiveSource() +{ + srs_freep(initialize_error_); +} + +srs_error_t MockOriginHubForLiveSource::initialize(SrsSharedPtr s, ISrsRequest *r) +{ + initialize_count_++; + return srs_error_copy(initialize_error_); +} + +void MockOriginHubForLiveSource::dispose() +{ +} + +srs_error_t MockOriginHubForLiveSource::cycle() +{ + return srs_success; +} + +bool MockOriginHubForLiveSource::active() +{ + return false; +} + +srs_utime_t MockOriginHubForLiveSource::cleanup_delay() +{ + return 0; +} + +srs_error_t MockOriginHubForLiveSource::on_meta_data(SrsMediaPacket *shared_metadata, SrsOnMetaDataPacket *packet) +{ + return srs_success; +} + +srs_error_t MockOriginHubForLiveSource::on_audio(SrsMediaPacket *shared_audio) +{ + return srs_success; +} + +srs_error_t MockOriginHubForLiveSource::on_video(SrsMediaPacket *shared_video, bool is_sequence_header) +{ + return srs_success; +} + +srs_error_t MockOriginHubForLiveSource::on_publish() +{ + return srs_success; +} + +void MockOriginHubForLiveSource::on_unpublish() +{ +} + +MockAppFactoryForLiveSource::MockAppFactoryForLiveSource() +{ + mock_hub_ = new MockOriginHubForLiveSource(); + create_origin_hub_count_ = 0; +} + +MockAppFactoryForLiveSource::~MockAppFactoryForLiveSource() +{ + srs_freep(mock_hub_); +} + +ISrsOriginHub *MockAppFactoryForLiveSource::create_origin_hub() +{ + create_origin_hub_count_++; + // Return the mock hub and transfer ownership + ISrsOriginHub *hub = mock_hub_; + mock_hub_ = NULL; + return hub; +} + +VOID TEST(SrsLiveSourceTest, InitializeOriginHubCreation) +{ + srs_error_t err; + + // Create mock config + MockAppConfig *mock_config = new MockAppConfig(); + + // Create mock factory + MockAppFactoryForLiveSource *mock_factory = new MockAppFactoryForLiveSource(); + + { + // Create live source + SrsLiveSource *source = new SrsLiveSource(); + + // Inject mock dependencies + source->config_ = mock_config; + source->app_factory_ = mock_factory; + + // Create mock request + MockSrsRequest mock_req("test.vhost", "live", "stream1"); + + // Create wrapper for shared pointer - this takes ownership + SrsSharedPtr wrapper(source); + + // Test typical origin server scenario (not edge) + // get_vhost_is_edge returns false by default in MockAppConfig + HELPER_EXPECT_SUCCESS(source->initialize(wrapper, &mock_req)); + + // Verify that factory was called to create origin hub + EXPECT_EQ(1, mock_factory->create_origin_hub_count_); + + // Verify that hub was created and initialized + EXPECT_TRUE(source->hub_ != NULL); + } + // Wrapper is destroyed here, which deletes the source, before mock objects + + // Cleanup + srs_freep(mock_config); + srs_freep(mock_factory); +} + +VOID TEST(SrsLiveSourceTest, ConsumerDumpsTypicalScenario) +{ + srs_error_t err; + + // Create mock config + MockAppConfig *mock_config = new MockAppConfig(); + + // Create mock factory with hub + MockAppFactoryForLiveSource *mock_factory = new MockAppFactoryForLiveSource(); + + { + // Create live source + SrsLiveSource *source = new SrsLiveSource(); + + // Inject mock dependencies + source->config_ = mock_config; + source->app_factory_ = mock_factory; + + // Create mock request + MockSrsRequest mock_req("test.vhost", "live", "stream1"); + + // Create wrapper for shared pointer + SrsSharedPtr wrapper(source); + + // Set hub to active state to test the typical publishing scenario + mock_factory->mock_hub_ = new MockOriginHubForLiveSource(); + source->hub_ = mock_factory->mock_hub_; + mock_factory->mock_hub_ = NULL; + + // Initialize the source + HELPER_EXPECT_SUCCESS(source->initialize(wrapper, &mock_req)); + + // Create a consumer + SrsLiveConsumer *consumer = NULL; + HELPER_EXPECT_SUCCESS(source->create_consumer(consumer)); + + // Test consumer_dumps with typical scenario (all parameters true) + HELPER_EXPECT_SUCCESS(source->consumer_dumps(consumer, true, true, true)); + + // Verify consumer was created + EXPECT_TRUE(consumer != NULL); + } + + // Cleanup + srs_freep(mock_config); + srs_freep(mock_factory); +} + +VOID TEST(SrsLiveSourceTest, OnMetaDataTypicalScenario) +{ + srs_error_t err; + + // Create mock config + MockAppConfig *mock_config = new MockAppConfig(); + + // Create mock factory with hub + MockAppFactoryForLiveSource *mock_factory = new MockAppFactoryForLiveSource(); + + { + // Create live source + SrsLiveSource *source = new SrsLiveSource(); + + // Inject mock dependencies + source->config_ = mock_config; + source->app_factory_ = mock_factory; + + // Create mock request + MockSrsRequest mock_req("test.vhost", "live", "stream1"); + + // Create wrapper for shared pointer + SrsSharedPtr wrapper(source); + + // Set hub to active state to test the typical publishing scenario + mock_factory->mock_hub_ = new MockOriginHubForLiveSource(); + source->hub_ = mock_factory->mock_hub_; + mock_factory->mock_hub_ = NULL; + + // Initialize the source + HELPER_EXPECT_SUCCESS(source->initialize(wrapper, &mock_req)); + + // Create a consumer + SrsLiveConsumer *consumer = NULL; + HELPER_EXPECT_SUCCESS(source->create_consumer(consumer)); + + // Create a mock RTMP message for metadata + SrsUniquePtr msg(new SrsRtmpCommonMessage()); + msg->header_.initialize_amf0_script(128, 0); + msg->create_payload(128); + memset(msg->payload(), 0x00, 128); + + // Create metadata packet with typical properties + SrsUniquePtr metadata(new SrsOnMetaDataPacket()); + metadata->metadata_->set("width", SrsAmf0Any::number(1920)); + metadata->metadata_->set("height", SrsAmf0Any::number(1080)); + metadata->metadata_->set("videocodecid", SrsAmf0Any::number(7)); // H.264 + metadata->metadata_->set("audiocodecid", SrsAmf0Any::number(10)); // AAC + + // Call on_meta_data and verify it succeeds + HELPER_EXPECT_SUCCESS(source->on_meta_data(msg.get(), metadata.get())); + + // Verify that metadata was cached + EXPECT_TRUE(source->meta()->data() != NULL); + + // Verify consumer was created + EXPECT_TRUE(consumer != NULL); + } + + // Cleanup + srs_freep(mock_config); + srs_freep(mock_factory); +} + +VOID TEST(GopCacheTest, ClearCacheWhenPureAudioOverflow) +{ + srs_error_t err; + + // Create a gop cache instance and enable it + SrsUniquePtr gop_cache(new SrsGopCache()); + gop_cache->set(true); + EXPECT_TRUE(gop_cache->enabled()); + + // Step 1: Cache a keyframe to establish that the stream has video + // This is critical - without video first, the stream would be detected as pure audio + // and caching would be disabled immediately + SrsUniquePtr keyframe(new MockH264VideoPacket(true)); + keyframe->timestamp_ = 1000; + HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe.get())); + EXPECT_FALSE(gop_cache->empty()); + EXPECT_FALSE(gop_cache->pure_audio()); // Stream has video, not pure audio + + // Step 2: Cache a few inter frames to build up the GOP + SrsUniquePtr interframe1(new MockH264VideoPacket(false)); + interframe1->timestamp_ = 1040; + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get())); + + SrsUniquePtr interframe2(new MockH264VideoPacket(false)); + interframe2->timestamp_ = 1080; + HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get())); + + // Step 3: Now simulate the scenario where video stops but audio continues + // This happens when a publisher disables their camera but keeps audio on + // We need to send MORE than SRS_PURE_AUDIO_GUESS_COUNT (115) audio packets + // to trigger the overflow detection and cache clearing + + // Send exactly 115 audio packets - should NOT trigger clearing yet + for (int i = 0; i < 115; i++) { + SrsUniquePtr audio(new MockAudioPacket()); + audio->timestamp_ = 1120 + (i * 26); // 26ms per audio packet (typical) + HELPER_EXPECT_SUCCESS(gop_cache->cache(audio.get())); + } + + // At this point, audio_after_last_video_count_ should be exactly 115 + // The cache should still have content (not cleared yet) + EXPECT_FALSE(gop_cache->empty()); + + // Step 4: Send ONE MORE audio packet to exceed the threshold + // This is the critical moment - audio_after_last_video_count_ becomes 116 + // which is > SRS_PURE_AUDIO_GUESS_COUNT (115) + // The code should detect this as "pure audio overflow" and clear the cache + SrsUniquePtr overflow_audio(new MockAudioPacket()); + overflow_audio->timestamp_ = 1120 + (115 * 26); + HELPER_EXPECT_SUCCESS(gop_cache->cache(overflow_audio.get())); + + // Step 5: Verify that the cache was cleared due to pure audio overflow + // This is the key assertion - the cache should be empty after exceeding the threshold + EXPECT_TRUE(gop_cache->empty()); + + // Step 6: Verify that the stream is now detected as pure audio + // After clearing, cached_video_count_ is reset to 0 + EXPECT_TRUE(gop_cache->pure_audio()); + + // Step 7: Verify that subsequent audio packets are NOT cached + // Once detected as pure audio, the cache should remain disabled + SrsUniquePtr post_clear_audio(new MockAudioPacket()); + post_clear_audio->timestamp_ = 1120 + (116 * 26); + HELPER_EXPECT_SUCCESS(gop_cache->cache(post_clear_audio.get())); + EXPECT_TRUE(gop_cache->empty()); // Should still be empty + + // Summary of what this test covers: + // 1. The "waiting" mechanism: audio_after_last_video_count_ incrementing with each audio packet + // 2. The threshold detection: exactly when count exceeds SRS_PURE_AUDIO_GUESS_COUNT (115) + // 3. The clearing action: gop cache is cleared when threshold is exceeded + // 4. The state transition: stream transitions from "has video" to "pure audio" + // 5. The prevention of future caching: once pure audio, no more caching occurs +} + +// Unit test for SrsOriginHub::on_video sequence header handling +// This test covers the "waiting" mechanism for video sequence headers +VOID TEST(AppOriginHubTest, OnVideoSequenceHeaderWaitingMechanism) +{ + srs_error_t err; + + // Create mock source with shared pointer + MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue(); + SrsSharedPtr source(raw_source); + + // Create mock request + MockHlsRequest mock_req; + + // Create origin hub and inject mock dependencies + SrsUniquePtr hub(new SrsOriginHub()); + + // Replace the default components with mocks + MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub(); + MockDashForOriginHub *mock_dash = new MockDashForOriginHub(); + MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub(); + MockStatisticForOriginHub *mock_stat = new MockStatisticForOriginHub(); + + // Access private members to inject mocks + srs_freep(hub->hls_); + hub->hls_ = mock_hls; + srs_freep(hub->dash_); + hub->dash_ = mock_dash; + srs_freep(hub->dvr_); + hub->dvr_ = mock_dvr; + hub->stat_ = mock_stat; + + // Initialize the hub + HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req)); + + // Create mock live source for origin hub with format + SrsUniquePtr mock_live_source(new MockLiveSourceForOriginHub()); + hub->source_ = mock_live_source.get(); + + // Test 1: AVC (H.264) sequence header - the "waiting" for sequence header + // This tests the code path: if (format->is_avc_sequence_header()) { if (c->id_ == SrsVideoCodecIdAVC) { ... } } + { + // Create H.264 video sequence header packet + SrsUniquePtr video_sh(new SrsMediaPacket()); + video_sh->timestamp_ = 0; + video_sh->message_type_ = SrsFrameTypeVideo; + + // Create minimal AVC sequence header packet + char *video_data = new char[10]; + video_data[0] = 0x17; // keyframe (0x10) + AVC (0x07) + video_data[1] = 0x00; // AVC sequence header + memset(video_data + 2, 0x00, 8); + video_sh->wrap(video_data, 10); + + // Manually set up the format state to simulate a parsed AVC sequence header + // This avoids the complex SPS/PPS parsing that would fail with invalid data + SrsRtmpFormat *format = mock_live_source->format(); + + // Initialize video codec config + if (!format->vcodec_) { + format->vcodec_ = new SrsVideoCodecConfig(); + } + if (!format->video_) { + format->video_ = new SrsParsedVideoPacket(); + } + + // Set up as AVC sequence header + format->vcodec_->id_ = SrsVideoCodecIdAVC; + format->video_->frame_type_ = SrsVideoAvcFrameTypeKeyFrame; + format->video_->avc_packet_type_ = SrsVideoAvcFrameTraitSequenceHeader; + + // Set codec parameters for testing + format->vcodec_->avc_profile_ = SrsAvcProfileBaseline; + format->vcodec_->avc_level_ = SrsAvcLevel_3; + format->vcodec_->width_ = 1920; + format->vcodec_->height_ = 1080; + format->vcodec_->video_data_rate_ = 2500000; // 2.5 Mbps + format->vcodec_->frame_rate_ = 30.0; + format->vcodec_->duration_ = 10.0; + + // Verify format detected AVC sequence header + EXPECT_TRUE(format->is_avc_sequence_header()); + EXPECT_TRUE(format->vcodec_ != NULL); + EXPECT_EQ(SrsVideoCodecIdAVC, format->vcodec_->id_); + + // Call on_video - this is where the "waiting" ends and processing happens + // The code waits for is_avc_sequence_header() to be true before calling stat_->on_video_info() + HELPER_EXPECT_SUCCESS(hub->on_video(video_sh.get(), true)); + + // Verify stat_->on_video_info() was called with correct AVC parameters + EXPECT_EQ(1, mock_stat->on_video_info_count_); + } + + // Test 2: HEVC (H.265) sequence header - the "waiting" for sequence header + // This tests the code path: if (format->is_avc_sequence_header()) { if (c->id_ == SrsVideoCodecIdHEVC) { ... } } + { + // Reset mock stat counter + mock_stat->on_video_info_count_ = 0; + + // Create H.265 video sequence header packet using enhanced-RTMP format + SrsUniquePtr video_sh(new SrsMediaPacket()); + video_sh->timestamp_ = 0; + video_sh->message_type_ = SrsFrameTypeVideo; + char *video_data = new char[10]; + video_data[0] = 0x90; // IsExHeader (0x80) | keyframe (0x10) | sequence start (0x00) + video_data[1] = 'h'; // fourcc 'hvc1' + video_data[2] = 'v'; + video_data[3] = 'c'; + video_data[4] = '1'; + memset(video_data + 5, 0x00, 5); + video_sh->wrap(video_data, 10); + + // Manually set up the format state to simulate a parsed HEVC sequence header + SrsRtmpFormat *format = mock_live_source->format(); + + // Initialize video codec config + if (!format->vcodec_) { + format->vcodec_ = new SrsVideoCodecConfig(); + } + if (!format->video_) { + format->video_ = new SrsParsedVideoPacket(); + } + + // Set up as HEVC sequence header + format->vcodec_->id_ = SrsVideoCodecIdHEVC; + format->video_->frame_type_ = SrsVideoAvcFrameTypeKeyFrame; + format->video_->avc_packet_type_ = SrsVideoAvcFrameTraitSequenceHeader; + + // Set codec parameters for testing + format->vcodec_->hevc_profile_ = SrsHevcProfileMain; + format->vcodec_->hevc_level_ = SrsHevcLevel_4; + format->vcodec_->width_ = 3840; + format->vcodec_->height_ = 2160; + format->vcodec_->video_data_rate_ = 10000000; // 10 Mbps + format->vcodec_->frame_rate_ = 60.0; + format->vcodec_->duration_ = 20.0; + + // Verify format detected HEVC sequence header + EXPECT_TRUE(format->is_avc_sequence_header()); // Note: is_avc_sequence_header() also returns true for HEVC + EXPECT_TRUE(format->vcodec_ != NULL); + EXPECT_EQ(SrsVideoCodecIdHEVC, format->vcodec_->id_); + + // Call on_video - this is where the "waiting" ends and processing happens + // The code waits for is_avc_sequence_header() to be true before calling stat_->on_video_info() + HELPER_EXPECT_SUCCESS(hub->on_video(video_sh.get(), true)); + + // Verify stat_->on_video_info() was called with correct HEVC parameters + EXPECT_EQ(1, mock_stat->on_video_info_count_); + } + + // Summary of what this test covers: + // 1. The "waiting" mechanism: code waits for format->is_avc_sequence_header() to return true + // 2. How it waits: checks is_avc_sequence_header() condition before processing codec info + // 3. AVC path: when c->id_ == SrsVideoCodecIdAVC, calls stat_->on_video_info() with AVC profile/level + // 4. HEVC path: when c->id_ == SrsVideoCodecIdHEVC, calls stat_->on_video_info() with HEVC profile/level + // 5. The trace logging: srs_trace() is called with codec-specific information (profile, level, resolution, bitrate, fps, duration) +} diff --git a/trunk/src/utest/srs_utest_app9.hpp b/trunk/src/utest/srs_utest_app9.hpp new file mode 100644 index 000000000..220f72555 --- /dev/null +++ b/trunk/src/utest/srs_utest_app9.hpp @@ -0,0 +1,333 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_UTEST_APP9_HPP +#define SRS_UTEST_APP9_HPP + +/* +#include +*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SRS_HDS +#include +#endif +#include + +// Mock media packet for testing jitter correction +class MockMediaPacketForJitter : public SrsMediaPacket +{ +public: + MockMediaPacketForJitter(int64_t timestamp, bool is_av); + virtual ~MockMediaPacketForJitter(); +}; + +// Mock live source for testing message queue dump_packets +class MockLiveSourceForQueue : public SrsLiveSource +{ +public: + MockLiveSourceForQueue(); + virtual ~MockLiveSourceForQueue(); + virtual void on_consumer_destroy(SrsLiveConsumer *consumer); + virtual srs_error_t initialize(SrsSharedPtr wrapper, ISrsRequest *r); + virtual void update_auth(ISrsRequest *r); +}; + +// Mock live consumer for testing message queue dump_packets +class MockLiveConsumerForQueue : public SrsLiveConsumer +{ +public: + int enqueue_count_; + std::vector enqueued_timestamps_; + +public: + MockLiveConsumerForQueue(MockLiveSourceForQueue *source); + virtual ~MockLiveConsumerForQueue(); + virtual srs_error_t enqueue(SrsMediaPacket *shared_msg, bool atc, SrsRtmpJitterAlgorithm ag); +}; + +// Mock media packet for testing gop cache with H.264 video +class MockH264VideoPacket : public SrsMediaPacket +{ +public: + MockH264VideoPacket(bool is_keyframe); + virtual ~MockH264VideoPacket(); +}; + +// Mock media packet for testing gop cache with audio +class MockAudioPacket : public SrsMediaPacket +{ +public: + MockAudioPacket(); + virtual ~MockAudioPacket(); +}; + +// Mock ISrsHls for testing SrsOriginHub::initialize +class MockHlsForOriginHub : public ISrsHls +{ +public: + int initialize_count_; + srs_error_t initialize_error_; + srs_utime_t cleanup_delay_; + int on_audio_count_; + int on_video_count_; + +public: + MockHlsForOriginHub(); + virtual ~MockHlsForOriginHub(); + virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r); + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format); + virtual srs_error_t on_video(SrsMediaPacket *shared_video, SrsFormat *format); + virtual srs_error_t on_publish(); + virtual void on_unpublish(); + virtual void dispose(); + virtual srs_error_t cycle(); + virtual srs_utime_t cleanup_delay(); +}; + +// Mock ISrsDash for testing SrsOriginHub::initialize +class MockDashForOriginHub : public ISrsDash +{ +public: + int initialize_count_; + srs_error_t initialize_error_; + srs_utime_t cleanup_delay_; + int on_audio_count_; + int on_video_count_; + +public: + MockDashForOriginHub(); + virtual ~MockDashForOriginHub(); + virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r); + virtual srs_error_t on_publish(); + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format); + virtual srs_error_t on_video(SrsMediaPacket *shared_video, SrsFormat *format); + virtual void on_unpublish(); + virtual void dispose(); + virtual srs_error_t cycle(); + virtual srs_utime_t cleanup_delay(); +}; + +// Mock ISrsDvr for testing SrsOriginHub::initialize +class MockDvrForOriginHub : public ISrsDvr +{ +public: + int initialize_count_; + srs_error_t initialize_error_; + int on_meta_data_count_; + int on_audio_count_; + int on_video_count_; + +public: + MockDvrForOriginHub(); + virtual ~MockDvrForOriginHub(); + virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r); + virtual srs_error_t on_publish(ISrsRequest *r); + virtual void on_unpublish(); + virtual srs_error_t on_meta_data(SrsMediaPacket *metadata); + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format); + virtual srs_error_t on_video(SrsMediaPacket *shared_video, SrsFormat *format); +}; + +// Mock ISrsForwarder for testing SrsOriginHub::on_meta_data +class MockForwarderForOriginHub : public ISrsForwarder +{ +public: + int on_meta_data_count_; + int on_audio_count_; + int on_video_count_; + +public: + MockForwarderForOriginHub(); + virtual ~MockForwarderForOriginHub(); + virtual srs_error_t initialize(ISrsRequest *r, std::string ep); + virtual void set_queue_size(srs_utime_t queue_size); + virtual srs_error_t on_publish(); + virtual void on_unpublish(); + virtual srs_error_t on_meta_data(SrsMediaPacket *shared_metadata); + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio); + virtual srs_error_t on_video(SrsMediaPacket *shared_video); +}; + +// Mock ISrsLiveSource for testing SrsOriginHub::on_audio +class MockLiveSourceForOriginHub : public ISrsLiveSource +{ +private: + SrsRtmpFormat *format_; + SrsMetaCache *meta_; + +public: + MockLiveSourceForOriginHub(); + virtual ~MockLiveSourceForOriginHub(); + virtual void on_consumer_destroy(SrsLiveConsumer *consumer); + virtual SrsContextId source_id(); + virtual SrsContextId pre_source_id(); + virtual SrsMetaCache *meta(); + virtual SrsRtmpFormat *format(); +}; + +// Mock ISrsStatistic for testing SrsOriginHub::on_video +class MockStatisticForOriginHub : public ISrsStatistic +{ +public: + int on_video_info_count_; + int on_audio_info_count_; + +public: + MockStatisticForOriginHub(); + virtual ~MockStatisticForOriginHub(); + 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); +}; + +// Mock ISrsNgExec for testing SrsOriginHub::on_publish +class MockNgExecForOriginHub : public ISrsNgExec +{ +public: + int on_publish_count_; + +public: + MockNgExecForOriginHub(); + virtual ~MockNgExecForOriginHub(); + virtual srs_error_t on_publish(ISrsRequest *req); + virtual void on_unpublish(); + virtual srs_error_t cycle(); +}; + +#ifdef SRS_HDS +// Mock ISrsHds for testing SrsOriginHub::on_publish +class MockHdsForOriginHub : public ISrsHds +{ +public: + int on_publish_count_; + +public: + MockHdsForOriginHub(); + virtual ~MockHdsForOriginHub(); + virtual srs_error_t on_publish(ISrsRequest *req); + virtual srs_error_t on_unpublish(); + virtual srs_error_t on_video(SrsMediaPacket *msg); + virtual srs_error_t on_audio(SrsMediaPacket *msg); +}; +#endif + +// Mock config for testing SrsOriginHub::create_forwarders +class MockAppConfigForForwarder : public MockAppConfig +{ +public: + SrsConfDirective *forwards_directive_; + SrsConfDirective *backend_directive_; + +public: + MockAppConfigForForwarder(); + virtual ~MockAppConfigForForwarder(); + virtual bool get_forward_enabled(std::string vhost); + virtual SrsConfDirective *get_forwards(std::string vhost); + virtual SrsConfDirective *get_forward_backend(std::string vhost); + void set_forward_destinations(const std::vector &destinations); + void set_forward_backend(const std::string &backend_url); +}; + +// Mock HTTP hooks for testing SrsOriginHub::create_backend_forwarders +class MockHttpHooksForBackend : public MockHttpHooks +{ +public: + std::vector backend_urls_; + int on_forward_backend_count_; + +public: + MockHttpHooksForBackend(); + virtual ~MockHttpHooksForBackend(); + virtual srs_error_t on_forward_backend(std::string url, ISrsRequest *req, std::vector &rtmp_urls); + void set_backend_urls(const std::vector &urls); +}; + +// Mock ISrsHourGlass for testing SrsLiveSourceManager::setup_ticks +class MockHourGlassForSourceManager : public ISrsHourGlass +{ +public: + int tick_event_; + srs_utime_t tick_interval_; + int tick_count_; + int start_count_; + srs_error_t tick_error_; + srs_error_t start_error_; + +public: + MockHourGlassForSourceManager(); + virtual ~MockHourGlassForSourceManager(); + virtual srs_error_t start(); + virtual void stop(); + virtual srs_error_t tick(srs_utime_t interval); + virtual srs_error_t tick(int event, srs_utime_t interval); + virtual void untick(int event); +}; + +// Mock SrsAppFactory for testing SrsLiveSourceManager::fetch_or_create +class MockAppFactoryForSourceManager : public SrsAppFactory +{ +public: + int create_live_source_count_; + +public: + MockAppFactoryForSourceManager(); + virtual ~MockAppFactoryForSourceManager(); + virtual SrsLiveSource *create_live_source(); +}; + +// Mock ISrsOriginHub for testing SrsLiveSource::initialize +class MockOriginHubForLiveSource : public ISrsOriginHub +{ +public: + int initialize_count_; + srs_error_t initialize_error_; + +public: + MockOriginHubForLiveSource(); + virtual ~MockOriginHubForLiveSource(); + virtual srs_error_t initialize(SrsSharedPtr s, ISrsRequest *r); + virtual void dispose(); + virtual srs_error_t cycle(); + virtual bool active(); + virtual srs_utime_t cleanup_delay(); + virtual srs_error_t on_meta_data(SrsMediaPacket *shared_metadata, SrsOnMetaDataPacket *packet); + virtual srs_error_t on_audio(SrsMediaPacket *shared_audio); + virtual srs_error_t on_video(SrsMediaPacket *shared_video, bool is_sequence_header); + virtual srs_error_t on_publish(); + virtual void on_unpublish(); +}; + +// Mock SrsAppFactory for testing SrsLiveSource::initialize +class MockAppFactoryForLiveSource : public SrsAppFactory +{ +public: + MockOriginHubForLiveSource *mock_hub_; + int create_origin_hub_count_; + +public: + MockAppFactoryForLiveSource(); + virtual ~MockAppFactoryForLiveSource(); + virtual ISrsOriginHub *create_origin_hub(); +}; + +#endif diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index ae0e6fbf1..dc9ec1ff9 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -202,12 +202,28 @@ public: } }; -bool _mock_srs_path_always_exists(std::string /*path*/) +MockSrsPathAlwaysExists::MockSrsPathAlwaysExists() +{ +} + +MockSrsPathAlwaysExists::~MockSrsPathAlwaysExists() +{ +} + +bool MockSrsPathAlwaysExists::exists(std::string /*path*/) { return true; } -bool _mock_srs_path_not_exists(std::string /*path*/) +MockSrsPathNotExists::MockSrsPathNotExists() +{ +} + +MockSrsPathNotExists::~MockSrsPathNotExists() +{ +} + +bool MockSrsPathNotExists::exists(std::string /*path*/) { return false; } @@ -1463,7 +1479,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory(fs)); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1486,7 +1502,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1503,7 +1519,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1521,7 +1537,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1539,7 +1555,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1557,7 +1573,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1575,7 +1591,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1597,7 +1613,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("livestream-13.ts")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1619,7 +1635,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("livestream-13.m4s")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1641,7 +1657,7 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) SrsVodStream h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("init.mp4")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1667,7 +1683,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) SrsHttpFileServer h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1684,7 +1700,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) SrsHttpFileServer h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1701,7 +1717,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) SrsHttpFileServer h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1718,7 +1734,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) SrsHttpFileServer h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1735,7 +1751,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) SrsHttpFileServer h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1752,7 +1768,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) SrsHttpFileServer h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1769,7 +1785,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) SrsHttpFileServer h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_always_exists); + h.set_path(new MockSrsPathAlwaysExists()); h.entry = &e; MockResponseWriter w; @@ -1786,7 +1802,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) SrsHttpFileServer h("/tmp"); h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); - h.set_path_check(_mock_srs_path_not_exists); + h.set_path(new MockSrsPathNotExists()); h.entry = &e; MockResponseWriter w; diff --git a/trunk/src/utest/srs_utest_http.hpp b/trunk/src/utest/srs_utest_http.hpp index 29bd573e5..bf2cc4491 100644 --- a/trunk/src/utest/srs_utest_http.hpp +++ b/trunk/src/utest/srs_utest_http.hpp @@ -59,6 +59,28 @@ string mock_http_response2(int status, string content); string mock_http_response4(int status, string content); bool is_string_contain(string substr, string str); +// Mock SrsPath that always returns true for exists() +class MockSrsPathAlwaysExists : public SrsPath +{ +public: + MockSrsPathAlwaysExists(); + virtual ~MockSrsPathAlwaysExists(); + +public: + virtual bool exists(std::string path); +}; + +// Mock SrsPath that always returns false for exists() +class MockSrsPathNotExists : public SrsPath +{ +public: + MockSrsPathNotExists(); + virtual ~MockSrsPathNotExists(); + +public: + virtual bool exists(std::string path); +}; + #define __MOCK_HTTP_EXPECT_STREQ(status, text, w) \ EXPECT_STREQ(mock_http_response(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str()) diff --git a/trunk/src/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index fa48637bb..d4c6652ff 100644 --- a/trunk/src/utest/srs_utest_kernel.cpp +++ b/trunk/src/utest/srs_utest_kernel.cpp @@ -2861,29 +2861,33 @@ VOID TEST(KernelUtility, BytesUtils) VOID TEST(KernelUtility, PathUtils) { if (true) { - EXPECT_TRUE("./" == srs_path_filepath_dir("")); - EXPECT_TRUE("/" == srs_path_filepath_dir("/")); - EXPECT_TRUE("/" == srs_path_filepath_dir("//")); - EXPECT_TRUE("/" == srs_path_filepath_dir("/stream")); - EXPECT_TRUE("live" == srs_path_filepath_dir("live/stream")); + SrsPath path; + EXPECT_TRUE("./" == path.filepath_dir("")); + EXPECT_TRUE("/" == path.filepath_dir("/")); + EXPECT_TRUE("/" == path.filepath_dir("//")); + EXPECT_TRUE("/" == path.filepath_dir("/stream")); + EXPECT_TRUE("live" == path.filepath_dir("live/stream")); } if (true) { - EXPECT_TRUE("" == srs_path_filepath_base("")); - EXPECT_TRUE("/" == srs_path_filepath_base("/")); - EXPECT_TRUE("stream" == srs_path_filepath_base("/stream")); - EXPECT_TRUE("stream" == srs_path_filepath_base("live/stream")); - EXPECT_TRUE("stream.flv" == srs_path_filepath_base("live/stream.flv")); + SrsPath path; + EXPECT_TRUE("" == path.filepath_base("")); + EXPECT_TRUE("/" == path.filepath_base("/")); + EXPECT_TRUE("stream" == path.filepath_base("/stream")); + EXPECT_TRUE("stream" == path.filepath_base("live/stream")); + EXPECT_TRUE("stream.flv" == path.filepath_base("live/stream.flv")); } if (true) { - EXPECT_TRUE("" == srs_path_filepath_filename("")); - EXPECT_TRUE("stream" == srs_path_filepath_filename("stream.flv")); + SrsPath path; + EXPECT_TRUE("" == path.filepath_filename("")); + EXPECT_TRUE("stream" == path.filepath_filename("stream.flv")); } if (true) { - EXPECT_TRUE("" == srs_path_filepath_ext("")); - EXPECT_TRUE(".flv" == srs_path_filepath_ext("stream.flv")); + SrsPath path; + EXPECT_TRUE("" == path.filepath_ext("")); + EXPECT_TRUE(".flv" == path.filepath_ext("stream.flv")); } } @@ -5551,7 +5555,8 @@ MockFileRemover::~MockFileRemover() return; if (path_.find(".log") == string::npos) return; - ::unlink(path_.c_str()); + SrsPath path; + path.unlink(path_); } VOID TEST(KernelFileTest, ReadWriteCase) @@ -6164,8 +6169,9 @@ VOID TEST(KernelUtilityTest, CoverTimeUtilityAll) } if (true) { - EXPECT_TRUE(srs_path_exists(".")); - HELPER_EXPECT_SUCCESS(srs_os_mkdir_all(".")); + SrsPath path; + EXPECT_TRUE(path.exists(".")); + HELPER_EXPECT_SUCCESS(path.mkdir_all(".")); } if (true) { diff --git a/trunk/src/utest/srs_utest_kernel3.cpp b/trunk/src/utest/srs_utest_kernel3.cpp index f8e8aafbd..a14dcc0ef 100644 --- a/trunk/src/utest/srs_utest_kernel3.cpp +++ b/trunk/src/utest/srs_utest_kernel3.cpp @@ -475,7 +475,7 @@ void MockSrsFastTimer::clear() } // Tests for srs_kernel_hourglass.hpp -VOID TEST(KernelHourglassTest, ISrsHourGlassInterface) +VOID TEST(KernelHourglassTest, ISrsHourGlassHandlerInterface) { MockSrsHourGlass handler; @@ -4001,7 +4001,7 @@ VOID TEST(KernelHourglassTest, SrsHourGlass_untick) { // Test SrsHourGlass::untick method // Create a mock handler for the hourglass - class MockHourGlassHandler : public ISrsHourGlass + class MockHourGlassHandler : public ISrsHourGlassHandler { public: srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick) @@ -4037,7 +4037,7 @@ VOID TEST(KernelHourglassTest, SrsHourGlass_stop) { // Test SrsHourGlass::stop method // Create a mock handler for the hourglass - class MockHourGlassHandler : public ISrsHourGlass + class MockHourGlassHandler : public ISrsHourGlassHandler { public: srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick) @@ -4141,7 +4141,8 @@ VOID TEST(KernelTSTest, SrsEncFileWriter_write) writer.close(); // Clean up temp file - unlink(temp_file.c_str()); + SrsPath path; + path.unlink(temp_file); } VOID TEST(KernelTSTest, SrsEncFileWriter_close) @@ -4184,7 +4185,8 @@ VOID TEST(KernelTSTest, SrsEncFileWriter_close) writer.close(); // Clean up - unlink(temp_file.c_str()); + SrsPath path; + path.unlink(temp_file); } VOID TEST(KernelTSTest, SrsTsMessageCache_do_cache_mp3) @@ -4286,7 +4288,8 @@ VOID TEST(KernelTSTest, SrsTsTransmuxer_set_has_video) srs_freep(err); } writer.close(); - unlink(temp_file.c_str()); + SrsPath path; + path.unlink(temp_file); } else { srs_freep(err); } diff --git a/trunk/src/utest/srs_utest_kernel3.hpp b/trunk/src/utest/srs_utest_kernel3.hpp index 218c61760..f7101f4fe 100644 --- a/trunk/src/utest/srs_utest_kernel3.hpp +++ b/trunk/src/utest/srs_utest_kernel3.hpp @@ -90,7 +90,7 @@ public: }; // Mock classes for hourglass testing -class MockSrsHourGlass : public ISrsHourGlass +class MockSrsHourGlass : public ISrsHourGlassHandler { public: std::vector events_; diff --git a/trunk/src/utest/srs_utest_protocol2.cpp b/trunk/src/utest/srs_utest_protocol2.cpp index 2e8e6cc45..9dc355a9a 100644 --- a/trunk/src/utest/srs_utest_protocol2.cpp +++ b/trunk/src/utest/srs_utest_protocol2.cpp @@ -4630,8 +4630,9 @@ VOID TEST(ProtocolKbpsTest, WriteLargeIOVs) iovs[0].iov_len = 5; MockBufferIO io; + SrsProtocolUtility utility; ssize_t nn = 0; - HELPER_EXPECT_SUCCESS(srs_write_large_iovs(&io, iovs, 1, &nn)); + HELPER_EXPECT_SUCCESS(utility.write_iovs(&io, iovs, 1, &nn)); EXPECT_EQ(5, nn); EXPECT_EQ(5, io.sbytes); } @@ -4645,8 +4646,9 @@ VOID TEST(ProtocolKbpsTest, WriteLargeIOVs) } MockBufferIO io; + SrsProtocolUtility utility; ssize_t nn = 0; - HELPER_EXPECT_SUCCESS(srs_write_large_iovs(&io, iovs, nn_iovs, &nn)); + HELPER_EXPECT_SUCCESS(utility.write_iovs(&io, iovs, nn_iovs, &nn)); EXPECT_EQ(5 * nn_iovs, nn); EXPECT_EQ(5 * nn_iovs, io.sbytes); } @@ -4660,8 +4662,9 @@ VOID TEST(ProtocolKbpsTest, WriteLargeIOVs) } MockBufferIO io; + SrsProtocolUtility utility; ssize_t nn = 0; - HELPER_EXPECT_SUCCESS(srs_write_large_iovs(&io, iovs, nn_iovs, &nn)); + HELPER_EXPECT_SUCCESS(utility.write_iovs(&io, iovs, nn_iovs, &nn)); EXPECT_EQ(5 * nn_iovs, nn); EXPECT_EQ(5 * nn_iovs, io.sbytes); } @@ -4675,8 +4678,9 @@ VOID TEST(ProtocolKbpsTest, WriteLargeIOVs) } MockBufferIO io; + SrsProtocolUtility utility; ssize_t nn = 0; - HELPER_EXPECT_SUCCESS(srs_write_large_iovs(&io, iovs, nn_iovs, &nn)); + HELPER_EXPECT_SUCCESS(utility.write_iovs(&io, iovs, nn_iovs, &nn)); EXPECT_EQ(5 * nn_iovs, nn); EXPECT_EQ(5 * nn_iovs, io.sbytes); } diff --git a/trunk/src/utest/srs_utest_service.cpp b/trunk/src/utest/srs_utest_service.cpp index 223bd42db..ccfcb30a4 100644 --- a/trunk/src/utest/srs_utest_service.cpp +++ b/trunk/src/utest/srs_utest_service.cpp @@ -1207,6 +1207,8 @@ VOID TEST(TCPServerTest, CoverUtility) EXPECT_FALSE(srs_net_url_is_rtmp("http://")); EXPECT_FALSE(srs_net_url_is_rtmp("rtmp:")); + SrsProtocolUtility utility; + // ipv4 loopback if (true) { addrinfo hints; @@ -1217,7 +1219,7 @@ VOID TEST(TCPServerTest, CoverUtility) ASSERT_TRUE(!getaddrinfo("127.0.0.1", NULL, &hints, &r_raw)); SrsUniquePtr r(r_raw, freeaddrinfo); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)r->ai_addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)r->ai_addr)); } // ipv4 intranet @@ -1230,7 +1232,7 @@ VOID TEST(TCPServerTest, CoverUtility) ASSERT_TRUE(!getaddrinfo("192.168.0.1", NULL, &hints, &r_raw)); SrsUniquePtr r(r_raw, freeaddrinfo); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)r->ai_addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)r->ai_addr)); } if (true) { @@ -1238,31 +1240,31 @@ VOID TEST(TCPServerTest, CoverUtility) addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(0x12000000); - EXPECT_TRUE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_TRUE(utility.is_internet((sockaddr *)&addr)); addr.sin_addr.s_addr = htonl(0x7f000000); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)&addr)); addr.sin_addr.s_addr = htonl(0x7f000001); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)&addr)); addr.sin_addr.s_addr = htonl(0x0a000000); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)&addr)); addr.sin_addr.s_addr = htonl(0x0a000001); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)&addr)); addr.sin_addr.s_addr = htonl(0x0affffff); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)&addr)); addr.sin_addr.s_addr = htonl(0xc0a80000); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)&addr)); addr.sin_addr.s_addr = htonl(0xc0a80001); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)&addr)); addr.sin_addr.s_addr = htonl(0xc0a8ffff); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)&addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)&addr)); } // Normal ipv6 address. @@ -1275,7 +1277,7 @@ VOID TEST(TCPServerTest, CoverUtility) ASSERT_TRUE(!getaddrinfo("2001:da8:6000:291:21f:d0ff:fed4:928c", NULL, &hints, &r_raw)); SrsUniquePtr r(r_raw, freeaddrinfo); - EXPECT_TRUE(srs_net_device_is_internet((sockaddr *)r->ai_addr)); + EXPECT_TRUE(utility.is_internet((sockaddr *)r->ai_addr)); } if (true) { addrinfo hints; @@ -1286,7 +1288,7 @@ VOID TEST(TCPServerTest, CoverUtility) ASSERT_TRUE(!getaddrinfo("3ffe:dead:beef::1", NULL, &hints, &r_raw)); SrsUniquePtr r(r_raw, freeaddrinfo); - EXPECT_TRUE(srs_net_device_is_internet((sockaddr *)r->ai_addr)); + EXPECT_TRUE(utility.is_internet((sockaddr *)r->ai_addr)); } // IN6_IS_ADDR_UNSPECIFIED @@ -1299,7 +1301,7 @@ VOID TEST(TCPServerTest, CoverUtility) ASSERT_TRUE(!getaddrinfo("::", NULL, &hints, &r_raw)); SrsUniquePtr r(r_raw, freeaddrinfo); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)r->ai_addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)r->ai_addr)); } // IN6_IS_ADDR_SITELOCAL @@ -1312,7 +1314,7 @@ VOID TEST(TCPServerTest, CoverUtility) ASSERT_TRUE(!getaddrinfo("fec0::", NULL, &hints, &r_raw)); SrsUniquePtr r(r_raw, freeaddrinfo); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)r->ai_addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)r->ai_addr)); } // IN6_IS_ADDR_LINKLOCAL @@ -1325,7 +1327,7 @@ VOID TEST(TCPServerTest, CoverUtility) ASSERT_TRUE(!getaddrinfo("FE80::", NULL, &hints, &r_raw)); SrsUniquePtr r(r_raw, freeaddrinfo); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)r->ai_addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)r->ai_addr)); } // IN6_IS_ADDR_LINKLOCAL @@ -1338,7 +1340,7 @@ VOID TEST(TCPServerTest, CoverUtility) ASSERT_TRUE(!getaddrinfo("::1", NULL, &hints, &r_raw)); SrsUniquePtr r(r_raw, freeaddrinfo); - EXPECT_FALSE(srs_net_device_is_internet((sockaddr *)r->ai_addr)); + EXPECT_FALSE(utility.is_internet((sockaddr *)r->ai_addr)); } } diff --git a/trunk/src/utest/srs_utest_srt.cpp b/trunk/src/utest/srs_utest_srt.cpp index ceaa11004..43dcdcdf6 100644 --- a/trunk/src/utest/srs_utest_srt.cpp +++ b/trunk/src/utest/srs_utest_srt.cpp @@ -457,12 +457,14 @@ VOID TEST(ProtocolSrtTest, SrtGetStreamInfoCompatible) VOID TEST(ProtocolSrtTest, SrtStreamIdToRequest) { + SrsProtocolUtility utility; + if (true) { SrtMode mode; SrsRequest req; EXPECT_TRUE(srs_srt_streamid_to_request("#!::r=live/livestream?key1=val1,key2=val2", mode, &req)); EXPECT_EQ(mode, SrtModePull); - EXPECT_STREQ(req.vhost_.c_str(), srs_get_public_internet_address().c_str()); + EXPECT_STREQ(req.vhost_.c_str(), utility.public_internet_address().c_str()); EXPECT_STREQ(req.app_.c_str(), "live"); EXPECT_STREQ(req.stream_.c_str(), "livestream"); EXPECT_STREQ(req.param_.c_str(), "key1=val1&key2=val2"); @@ -484,7 +486,7 @@ VOID TEST(ProtocolSrtTest, SrtStreamIdToRequest) SrsRequest req; EXPECT_TRUE(srs_srt_streamid_to_request("#!::h=live/livestream?key1=val1,key2=val2", mode, &req)); EXPECT_EQ(mode, SrtModePull); - EXPECT_STREQ(req.vhost_.c_str(), srs_get_public_internet_address().c_str()); + EXPECT_STREQ(req.vhost_.c_str(), utility.public_internet_address().c_str()); EXPECT_STREQ(req.app_.c_str(), "live"); EXPECT_STREQ(req.stream_.c_str(), "livestream"); EXPECT_STREQ(req.param_.c_str(), "key1=val1&key2=val2"); diff --git a/trunk/src/utest/srs_utest_st.cpp b/trunk/src/utest/srs_utest_st.cpp index 13fd75f83..faa261bb2 100644 --- a/trunk/src/utest/srs_utest_st.cpp +++ b/trunk/src/utest/srs_utest_st.cpp @@ -502,7 +502,8 @@ VOID TEST(StSocketTest, RandomPortTcpListenAndConnect) srs_error_t err; // Generate random port in range [30000, 60000] - int random_port = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); + SrsRand rand; + int random_port = 30000 + (rand.integer() % (60000 - 30000 + 1)); EXPECT_GE(random_port, 30000); EXPECT_LE(random_port, 60000); @@ -534,7 +535,8 @@ VOID TEST(StSocketTest, RandomPortTcpListenAndConnectIPv6) srs_error_t err; // Generate random port in range [30000, 60000] - int random_port = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); + SrsRand rand; + int random_port = 30000 + (rand.integer() % (60000 - 30000 + 1)); EXPECT_GE(random_port, 30000); EXPECT_LE(random_port, 60000); @@ -565,7 +567,8 @@ VOID TEST(StSocketTest, RandomPortUdpListenIPv4) srs_error_t err; // Generate random port in range [30000, 60000] - int random_port = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); + SrsRand rand; + int random_port = 30000 + (rand.integer() % (60000 - 30000 + 1)); EXPECT_GE(random_port, 30000); EXPECT_LE(random_port, 60000); @@ -597,7 +600,8 @@ VOID TEST(StSocketTest, RandomPortUdpListenIPv6) srs_error_t err; // Generate random port in range [30000, 60000] - int random_port = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); + SrsRand rand; + int random_port = 30000 + (rand.integer() % (60000 - 30000 + 1)); EXPECT_GE(random_port, 30000); EXPECT_LE(random_port, 60000);