AI: Improve the coverage for app hls module.

This commit is contained in:
OSSRS-AI 2025-09-30 07:36:02 -04:00 committed by winlin
parent df3c776580
commit 3f876d324e
19 changed files with 2810 additions and 167 deletions

5
trunk/configure vendored
View File

@ -377,12 +377,13 @@ fi
if [[ $SRS_UTEST == YES ]]; then if [[ $SRS_UTEST == YES ]]; then
MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_kernel" "srs_utest_core" MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_kernel" "srs_utest_core"
"srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload" "srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload"
"srs_utest_mp4" "srs_utest_service" "srs_utest_app" "srs_utest_app2" "srs_utest_rtc" "srs_utest_config2" "srs_utest_mp4" "srs_utest_service" "srs_utest_app_rtc2rtmp" "srs_utest_rtc" "srs_utest_config2"
"srs_utest_config3" "srs_utest_config4" "srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_config3" "srs_utest_config4" "srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2"
"srs_utest_st" "srs_utest_rtc2" "srs_utest_rtc3" "srs_utest_fmp4" "srs_utest_source_lock" "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_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_coworkers" "srs_utest_pithy_print" "srs_utest_kernel3" "srs_utest_protocol4"
"srs_utest_protocol3" "srs_utest_app3" "srs_utest_app4" "srs_utest_app5" "srs_utest_app6" "srs_utest_app7" "srs_utest_app_rtc2rtmp") "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")
# Always include SRT utest # Always include SRT utest
MODULE_FILES+=("srs_utest_srt") MODULE_FILES+=("srs_utest_srt")
if [[ $SRS_GB28181 == YES ]]; then if [[ $SRS_GB28181 == YES ]]; then

View File

@ -296,6 +296,36 @@ public:
virtual bool get_rtc_to_rtmp(std::string vhost) = 0; virtual bool get_rtc_to_rtmp(std::string vhost) = 0;
virtual srs_utime_t get_rtc_stun_timeout(std::string vhost) = 0; virtual srs_utime_t get_rtc_stun_timeout(std::string vhost) = 0;
virtual bool get_rtc_stun_strict_check(std::string vhost) = 0; virtual bool get_rtc_stun_strict_check(std::string vhost) = 0;
virtual SrsConfDirective *get_vhost_on_hls(std::string vhost) = 0;
virtual SrsConfDirective *get_vhost_on_hls_notify(std::string vhost) = 0;
virtual bool get_hls_enabled(std::string vhost) = 0;
virtual bool get_hls_enabled(SrsConfDirective *vhost) = 0;
virtual bool get_hls_use_fmp4(std::string vhost) = 0;
virtual std::string get_hls_entry_prefix(std::string vhost) = 0;
virtual std::string get_hls_path(std::string vhost) = 0;
virtual std::string get_hls_m3u8_file(std::string vhost) = 0;
virtual std::string get_hls_ts_file(std::string vhost) = 0;
virtual std::string get_hls_fmp4_file(std::string vhost) = 0;
virtual std::string get_hls_init_file(std::string vhost) = 0;
virtual bool get_hls_ts_floor(std::string vhost) = 0;
virtual srs_utime_t get_hls_fragment(std::string vhost) = 0;
virtual double get_hls_td_ratio(std::string vhost) = 0;
virtual double get_hls_aof_ratio(std::string vhost) = 0;
virtual srs_utime_t get_hls_window(std::string vhost) = 0;
virtual std::string get_hls_on_error(std::string vhost) = 0;
virtual bool get_hls_cleanup(std::string vhost) = 0;
virtual srs_utime_t get_hls_dispose(std::string vhost) = 0;
virtual bool get_hls_wait_keyframe(std::string vhost) = 0;
virtual bool get_hls_keys(std::string vhost) = 0;
virtual int get_hls_fragments_per_key(std::string vhost) = 0;
virtual std::string get_hls_key_file(std::string vhost) = 0;
virtual std::string get_hls_key_file_path(std::string vhost) = 0;
virtual std::string get_hls_key_url(std::string vhost) = 0;
virtual int get_vhost_hls_nb_notify(std::string vhost) = 0;
virtual bool get_vhost_hls_dts_directly(std::string vhost) = 0;
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;
}; };
// The config service provider. // The config service provider.

View File

@ -8,7 +8,38 @@
#include <srs_app_config.hpp> #include <srs_app_config.hpp>
#include <srs_app_st.hpp> #include <srs_app_st.hpp>
#include <srs_kernel_file.hpp>
#include <srs_protocol_st.hpp> #include <srs_protocol_st.hpp>
#include <srs_kernel_ts.hpp>
#include <srs_kernel_utility.hpp>
SrsAppFactory::SrsAppFactory()
{
}
SrsAppFactory::~SrsAppFactory()
{
}
ISrsFileWriter *SrsAppFactory::create_file_writer()
{
return new SrsFileWriter();
}
ISrsFileWriter *SrsAppFactory::create_enc_file_writer()
{
return new SrsEncFileWriter();
}
ISrsFileReader *SrsAppFactory::create_file_reader()
{
return new SrsFileReader();
}
SrsPath *SrsAppFactory::create_path()
{
return new SrsPath();
}
SrsFinalFactory::SrsFinalFactory() SrsFinalFactory::SrsFinalFactory()
{ {

View File

@ -11,6 +11,26 @@
#include <srs_kernel_factory.hpp> #include <srs_kernel_factory.hpp>
class ISrsFileWriter;
class ISrsFileReader;
class SrsPath;
// The factory to create app objects.
class SrsAppFactory
{
public:
SrsAppFactory();
virtual ~SrsAppFactory();
public:
virtual ISrsFileWriter *create_file_writer();
virtual ISrsFileWriter *create_enc_file_writer();
virtual ISrsFileReader *create_file_reader();
virtual SrsPath *create_path();
};
extern SrsAppFactory *_srs_app_factory;
// The factory to create kernel objects. // The factory to create kernel objects.
class SrsFinalFactory : public ISrsKernelFactory class SrsFinalFactory : public ISrsKernelFactory
{ {

View File

@ -19,6 +19,7 @@ using namespace std;
#include <openssl/rand.h> #include <openssl/rand.h>
#include <srs_app_config.hpp> #include <srs_app_config.hpp>
#include <srs_app_factory.hpp>
#include <srs_app_http_hooks.hpp> #include <srs_app_http_hooks.hpp>
#include <srs_app_rtmp_source.hpp> #include <srs_app_rtmp_source.hpp>
#include <srs_app_utility.hpp> #include <srs_app_utility.hpp>
@ -44,7 +45,7 @@ using namespace std;
// reset the piece id when deviation overflow this. // reset the piece id when deviation overflow this.
#define SRS_JUMP_WHEN_PIECE_DEVIATION 20 #define SRS_JUMP_WHEN_PIECE_DEVIATION 20
SrsHlsSegment::SrsHlsSegment(SrsTsContext *c, SrsAudioCodecId ac, SrsVideoCodecId vc, SrsFileWriter *w) SrsHlsSegment::SrsHlsSegment(SrsTsContext *c, SrsAudioCodecId ac, SrsVideoCodecId vc, ISrsFileWriter *w)
{ {
sequence_no_ = 0; sequence_no_ = 0;
writer_ = w; writer_ = w;
@ -60,7 +61,8 @@ void SrsHlsSegment::config_cipher(unsigned char *key, unsigned char *iv)
{ {
memcpy(this->iv_, iv, 16); memcpy(this->iv_, iv, 16);
SrsEncFileWriter *fw = (SrsEncFileWriter *)writer_; SrsEncFileWriter *fw = dynamic_cast<SrsEncFileWriter *>(writer_);
srs_assert(fw);
fw->config_cipher(key, iv); fw->config_cipher(key, iv);
} }
@ -75,7 +77,7 @@ srs_error_t SrsHlsSegment::rename()
return SrsFragment::rename(); return SrsFragment::rename();
} }
SrsInitMp4Segment::SrsInitMp4Segment(SrsFileWriter *fw) SrsInitMp4Segment::SrsInitMp4Segment(ISrsFileWriter *fw)
{ {
fw_ = fw; fw_ = fw;
const_iv_size_ = 0; const_iv_size_ = 0;
@ -165,7 +167,7 @@ srs_error_t SrsInitMp4Segment::init_encoder()
return err; return err;
} }
SrsHlsM4sSegment::SrsHlsM4sSegment(SrsFileWriter *fw) SrsHlsM4sSegment::SrsHlsM4sSegment(ISrsFileWriter *fw)
{ {
fw_ = fw; fw_ = fw;
} }
@ -264,18 +266,24 @@ SrsDvrAsyncCallOnHls::SrsDvrAsyncCallOnHls(SrsContextId c, ISrsRequest *r, strin
m3u8_url_ = mu; m3u8_url_ = mu;
seq_no_ = s; seq_no_ = s;
duration_ = d; duration_ = d;
config_ = _srs_config;
hooks_ = _srs_hooks;
} }
SrsDvrAsyncCallOnHls::~SrsDvrAsyncCallOnHls() SrsDvrAsyncCallOnHls::~SrsDvrAsyncCallOnHls()
{ {
srs_freep(req_); srs_freep(req_);
config_ = NULL;
hooks_ = NULL;
} }
srs_error_t SrsDvrAsyncCallOnHls::call() srs_error_t SrsDvrAsyncCallOnHls::call()
{ {
srs_error_t err = srs_success; srs_error_t err = srs_success;
if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost_)) { if (!config_->get_vhost_http_hooks_enabled(req_->vhost_)) {
return err; return err;
} }
@ -285,7 +293,7 @@ srs_error_t SrsDvrAsyncCallOnHls::call()
vector<string> hooks; vector<string> hooks;
if (true) { if (true) {
SrsConfDirective *conf = _srs_config->get_vhost_on_hls(req_->vhost_); SrsConfDirective *conf = config_->get_vhost_on_hls(req_->vhost_);
if (!conf) { if (!conf) {
return err; return err;
@ -296,7 +304,7 @@ srs_error_t SrsDvrAsyncCallOnHls::call()
for (int i = 0; i < (int)hooks.size(); i++) { for (int i = 0; i < (int)hooks.size(); i++) {
std::string url = hooks.at(i); std::string url = hooks.at(i);
if ((err = _srs_hooks->on_hls(cid_, url, req_, path_, ts_url_, m3u8_, m3u8_url_, seq_no_, duration_)) != srs_success) { if ((err = hooks_->on_hls(cid_, url, req_, path_, ts_url_, m3u8_, m3u8_url_, seq_no_, duration_)) != srs_success) {
return srs_error_wrap(err, "callback on_hls %s", url.c_str()); return srs_error_wrap(err, "callback on_hls %s", url.c_str());
} }
} }
@ -314,18 +322,24 @@ SrsDvrAsyncCallOnHlsNotify::SrsDvrAsyncCallOnHlsNotify(SrsContextId c, ISrsReque
cid_ = c; cid_ = c;
req_ = r->copy(); req_ = r->copy();
ts_url_ = u; ts_url_ = u;
config_ = _srs_config;
hooks_ = _srs_hooks;
} }
SrsDvrAsyncCallOnHlsNotify::~SrsDvrAsyncCallOnHlsNotify() SrsDvrAsyncCallOnHlsNotify::~SrsDvrAsyncCallOnHlsNotify()
{ {
srs_freep(req_); srs_freep(req_);
config_ = NULL;
hooks_ = NULL;
} }
srs_error_t SrsDvrAsyncCallOnHlsNotify::call() srs_error_t SrsDvrAsyncCallOnHlsNotify::call()
{ {
srs_error_t err = srs_success; srs_error_t err = srs_success;
if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost_)) { if (!config_->get_vhost_http_hooks_enabled(req_->vhost_)) {
return err; return err;
} }
@ -335,7 +349,7 @@ srs_error_t SrsDvrAsyncCallOnHlsNotify::call()
vector<string> hooks; vector<string> hooks;
if (true) { if (true) {
SrsConfDirective *conf = _srs_config->get_vhost_on_hls_notify(req_->vhost_); SrsConfDirective *conf = config_->get_vhost_on_hls_notify(req_->vhost_);
if (!conf) { if (!conf) {
return err; return err;
@ -344,10 +358,10 @@ srs_error_t SrsDvrAsyncCallOnHlsNotify::call()
hooks = conf->args_; hooks = conf->args_;
} }
int nb_notify = _srs_config->get_vhost_hls_nb_notify(req_->vhost_); int nb_notify = config_->get_vhost_hls_nb_notify(req_->vhost_);
for (int i = 0; i < (int)hooks.size(); i++) { for (int i = 0; i < (int)hooks.size(); i++) {
std::string url = hooks.at(i); std::string url = hooks.at(i);
if ((err = _srs_hooks->on_hls_notify(cid_, url, req_, ts_url_, nb_notify)) != srs_success) { if ((err = hooks_->on_hls_notify(cid_, url, req_, ts_url_, nb_notify)) != srs_success) {
return srs_error_wrap(err, "callback on_hls_notify %s", url.c_str()); return srs_error_wrap(err, "callback on_hls_notify %s", url.c_str());
} }
} }
@ -388,6 +402,9 @@ SrsHlsFmp4Muxer::SrsHlsFmp4Muxer()
memset(key_, 0, 16); memset(key_, 0, 16);
memset(iv_, 0, 16); memset(iv_, 0, 16);
config_ = _srs_config;
app_factory_ = _srs_app_factory;
} }
SrsHlsFmp4Muxer::~SrsHlsFmp4Muxer() SrsHlsFmp4Muxer::~SrsHlsFmp4Muxer()
@ -397,6 +414,9 @@ SrsHlsFmp4Muxer::~SrsHlsFmp4Muxer()
srs_freep(req_); srs_freep(req_);
srs_freep(async_); srs_freep(async_);
srs_freep(writer_); srs_freep(writer_);
config_ = NULL;
app_factory_ = NULL;
} }
void SrsHlsFmp4Muxer::dispose() void SrsHlsFmp4Muxer::dispose()
@ -413,8 +433,12 @@ void SrsHlsFmp4Muxer::dispose()
srs_freep(current_); srs_freep(current_);
} }
if (unlink(m3u8_.c_str()) < 0) { SrsUniquePtr<SrsPath> path(app_factory_->create_path());
srs_warn("dispose unlink path failed. file=%s", m3u8_.c_str()); if (path->exists(m3u8_)) {
if ((err = path->unlink(m3u8_)) != srs_success) {
srs_warn("dispose: ignore remove m3u8 failed, %s", srs_error_desc(err).c_str());
srs_freep(err);
}
} }
srs_trace("gracefully dispose hls %s", req_ ? req_->get_stream_url().c_str() : ""); srs_trace("gracefully dispose hls %s", req_ ? req_->get_stream_url().c_str() : "");
@ -499,10 +523,10 @@ srs_error_t SrsHlsFmp4Muxer::write_init_mp4(SrsFormat *format, bool has_video, b
std::string app = req_->app_; std::string app = req_->app_;
// Get init.mp4 file template from configuration // Get init.mp4 file template from configuration
std::string init_file = _srs_config->get_hls_init_file(vhost); std::string init_file = config_->get_hls_init_file(vhost);
init_file = srs_path_build_stream(init_file, vhost, app, stream); init_file = srs_path_build_stream(init_file, vhost, app, stream);
std::string hls_path = _srs_config->get_hls_path(vhost); std::string hls_path = config_->get_hls_path(vhost);
std::string path = hls_path + "/" + init_file; std::string path = hls_path + "/" + init_file;
// Create directory for the init file // Create directory for the init file
@ -649,28 +673,28 @@ srs_error_t SrsHlsFmp4Muxer::update_config(ISrsRequest *r)
std::string stream = req_->stream_; std::string stream = req_->stream_;
std::string app = req_->app_; std::string app = req_->app_;
hls_fragment_ = _srs_config->get_hls_fragment(vhost); hls_fragment_ = config_->get_hls_fragment(vhost);
double hls_td_ratio = _srs_config->get_hls_td_ratio(vhost); double hls_td_ratio = config_->get_hls_td_ratio(vhost);
hls_window_ = _srs_config->get_hls_window(vhost); hls_window_ = config_->get_hls_window(vhost);
// get the hls m3u8 ts list entry prefix config // get the hls m3u8 ts list entry prefix config
hls_entry_prefix_ = _srs_config->get_hls_entry_prefix(vhost); hls_entry_prefix_ = config_->get_hls_entry_prefix(vhost);
// get the hls path config // get the hls path config
hls_path_ = _srs_config->get_hls_path(vhost); hls_path_ = config_->get_hls_path(vhost);
m3u8_url_ = _srs_config->get_hls_m3u8_file(vhost); m3u8_url_ = config_->get_hls_m3u8_file(vhost);
hls_m4s_file_ = _srs_config->get_hls_fmp4_file(vhost); hls_m4s_file_ = config_->get_hls_fmp4_file(vhost);
hls_cleanup_ = _srs_config->get_hls_cleanup(vhost); hls_cleanup_ = config_->get_hls_cleanup(vhost);
hls_wait_keyframe_ = _srs_config->get_hls_wait_keyframe(vhost); hls_wait_keyframe_ = config_->get_hls_wait_keyframe(vhost);
// the audio overflow, for pure audio to reap segment. // the audio overflow, for pure audio to reap segment.
hls_aof_ratio_ = _srs_config->get_hls_aof_ratio(vhost); hls_aof_ratio_ = config_->get_hls_aof_ratio(vhost);
// whether use floor(timestamp/hls_fragment) for variable timestamp // whether use floor(timestamp/hls_fragment) for variable timestamp
hls_ts_floor_ = _srs_config->get_hls_ts_floor(vhost); hls_ts_floor_ = config_->get_hls_ts_floor(vhost);
hls_keys_ = _srs_config->get_hls_keys(vhost); hls_keys_ = config_->get_hls_keys(vhost);
hls_fragments_per_key_ = _srs_config->get_hls_fragments_per_key(vhost); hls_fragments_per_key_ = config_->get_hls_fragments_per_key(vhost);
hls_key_file_ = _srs_config->get_hls_key_file(vhost); hls_key_file_ = config_->get_hls_key_file(vhost);
hls_key_file_path_ = _srs_config->get_hls_key_file_path(vhost); hls_key_file_path_ = config_->get_hls_key_file_path(vhost);
hls_key_url_ = _srs_config->get_hls_key_url(vhost); hls_key_url_ = config_->get_hls_key_url(vhost);
previous_floor_ts_ = 0; previous_floor_ts_ = 0;
accept_floor_ts_ = 0; accept_floor_ts_ = 0;
@ -698,7 +722,7 @@ srs_error_t SrsHlsFmp4Muxer::update_config(ISrsRequest *r)
} }
} }
writer_ = new SrsFileWriter(); writer_ = app_factory_->create_file_writer();
return err; return err;
} }
@ -907,13 +931,13 @@ srs_error_t SrsHlsFmp4Muxer::write_hls_key()
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(current_->sequence_no_)); key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(current_->sequence_no_));
string key_url = hls_key_file_path_ + "/" + key_file; string key_url = hls_key_file_path_ + "/" + key_file;
SrsFileWriter fw; SrsUniquePtr<ISrsFileWriter> fw(app_factory_->create_file_writer());
if ((err = fw.open(key_url)) != srs_success) { if ((err = fw->open(key_url)) != srs_success) {
return srs_error_wrap(err, "open file %s", key_url.c_str()); return srs_error_wrap(err, "open file %s", key_url.c_str());
} }
err = fw.write(key_, 16, NULL); err = fw->write(key_, 16, NULL);
fw.close(); fw->close();
if (err != srs_success) { if (err != srs_success) {
return srs_error_wrap(err, "write key"); return srs_error_wrap(err, "write key");
@ -937,23 +961,25 @@ srs_error_t SrsHlsFmp4Muxer::refresh_m3u8()
} }
std::string temp_m3u8 = m3u8_ + ".temp"; std::string temp_m3u8 = m3u8_ + ".temp";
if ((err = _refresh_m3u8(temp_m3u8)) == srs_success) { if ((err = do_refresh_m3u8(temp_m3u8)) == srs_success) {
if (rename(temp_m3u8.c_str(), m3u8_.c_str()) < 0) { if (rename(temp_m3u8.c_str(), m3u8_.c_str()) < 0) {
err = srs_error_new(ERROR_HLS_WRITE_FAILED, "hls: rename m3u8 file failed. %s => %s", temp_m3u8.c_str(), m3u8_.c_str()); err = srs_error_new(ERROR_HLS_WRITE_FAILED, "hls: rename m3u8 file failed. %s => %s", temp_m3u8.c_str(), m3u8_.c_str());
} }
} }
// remove the temp file. // remove the temp file.
if (srs_path_exists(temp_m3u8)) { SrsUniquePtr<SrsPath> path(app_factory_->create_path());
if (unlink(temp_m3u8.c_str()) < 0) { if (path->exists(temp_m3u8)) {
srs_warn("ignore remove m3u8 failed, %s", temp_m3u8.c_str()); if ((err = path->unlink(temp_m3u8)) != srs_success) {
srs_warn("refresh: ignore remove m3u8 failed, %s", srs_error_desc(err).c_str());
srs_freep(err);
} }
} }
return err; return err;
} }
srs_error_t SrsHlsFmp4Muxer::_refresh_m3u8(std::string m3u8_file) srs_error_t SrsHlsFmp4Muxer::do_refresh_m3u8(std::string m3u8_file)
{ {
srs_error_t err = srs_success; srs_error_t err = srs_success;
@ -962,8 +988,8 @@ srs_error_t SrsHlsFmp4Muxer::_refresh_m3u8(std::string m3u8_file)
return err; return err;
} }
SrsFileWriter writer; SrsUniquePtr<ISrsFileWriter> writer(app_factory_->create_file_writer());
if ((err = writer.open(m3u8_file)) != srs_success) { if ((err = writer->open(m3u8_file)) != srs_success) {
return srs_error_wrap(err, "hls: open m3u8 file %s", m3u8_file.c_str()); return srs_error_wrap(err, "hls: open m3u8 file %s", m3u8_file.c_str());
} }
@ -1048,7 +1074,7 @@ srs_error_t SrsHlsFmp4Muxer::_refresh_m3u8(std::string m3u8_file)
// write m3u8 to writer. // write m3u8 to writer.
std::string m3u8 = ss.str(); std::string m3u8 = ss.str();
if ((err = writer.write((char *)m3u8.c_str(), (int)m3u8.length(), NULL)) != srs_success) { if ((err = writer->write((char *)m3u8.c_str(), (int)m3u8.length(), NULL)) != srs_success) {
return srs_error_wrap(err, "hls: write m3u8"); return srs_error_wrap(err, "hls: write m3u8");
} }
@ -1080,6 +1106,9 @@ SrsHlsMuxer::SrsHlsMuxer()
memset(key_, 0, 16); memset(key_, 0, 16);
memset(iv_, 0, 16); memset(iv_, 0, 16);
config_ = _srs_config;
app_factory_ = _srs_app_factory;
} }
SrsHlsMuxer::~SrsHlsMuxer() SrsHlsMuxer::~SrsHlsMuxer()
@ -1090,6 +1119,9 @@ SrsHlsMuxer::~SrsHlsMuxer()
srs_freep(async_); srs_freep(async_);
srs_freep(context_); srs_freep(context_);
srs_freep(writer_); srs_freep(writer_);
config_ = NULL;
app_factory_ = NULL;
} }
void SrsHlsMuxer::dispose() void SrsHlsMuxer::dispose()
@ -1106,8 +1138,12 @@ void SrsHlsMuxer::dispose()
srs_freep(current_); srs_freep(current_);
} }
if (unlink(m3u8_.c_str()) < 0) { SrsUniquePtr<SrsPath> path(app_factory_->create_path());
srs_warn("dispose unlink path failed. file=%s", m3u8_.c_str()); if (path->exists(m3u8_)) {
if ((err = path->unlink(m3u8_)) != srs_success) {
srs_warn("dispose: ignore remove m3u8 failed, %s", srs_error_desc(err).c_str());
srs_freep(err);
}
} }
srs_trace("gracefully dispose hls %s", req_ ? req_->get_stream_url().c_str() : ""); srs_trace("gracefully dispose hls %s", req_ ? req_->get_stream_url().c_str() : "");
@ -1240,7 +1276,7 @@ srs_error_t SrsHlsMuxer::update_config(ISrsRequest *r, string entry_prefix,
m3u8_ = path + "/" + m3u8_url_; m3u8_ = path + "/" + m3u8_url_;
// when update config, reset the history target duration. // when update config, reset the history target duration.
max_td_ = fragment * _srs_config->get_hls_td_ratio(r->vhost_); max_td_ = fragment * config_->get_hls_td_ratio(r->vhost_);
// create m3u8 dir once. // create m3u8 dir once.
m3u8_dir_ = srs_path_filepath_dir(m3u8_); m3u8_dir_ = srs_path_filepath_dir(m3u8_);
@ -1258,9 +1294,9 @@ srs_error_t SrsHlsMuxer::update_config(ISrsRequest *r, string entry_prefix,
} }
if (hls_keys_) { if (hls_keys_) {
writer_ = new SrsEncFileWriter(); writer_ = app_factory_->create_enc_file_writer();
} else { } else {
writer_ = new SrsFileWriter(); writer_ = app_factory_->create_file_writer();
} }
return err; return err;
@ -1271,21 +1307,29 @@ srs_error_t SrsHlsMuxer::recover_hls()
srs_error_t err = srs_success; srs_error_t err = srs_success;
// exist the m3u8 file. // exist the m3u8 file.
if (!srs_path_exists(m3u8_)) { SrsUniquePtr<SrsPath> path(app_factory_->create_path());
if (!path->exists(m3u8_)) {
return err; return err;
} }
return do_recover_hls();
}
srs_error_t SrsHlsMuxer::do_recover_hls()
{
srs_error_t err = srs_success;
srs_trace("hls: recover stream m3u8=%s, m3u8_url=%s, hls_path=%s", srs_trace("hls: recover stream m3u8=%s, m3u8_url=%s, hls_path=%s",
m3u8_.c_str(), m3u8_url_.c_str(), hls_path_.c_str()); m3u8_.c_str(), m3u8_url_.c_str(), hls_path_.c_str());
// read whole m3u8 file content as a string // read whole m3u8 file content as a string
SrsFileReader fr; SrsUniquePtr<ISrsFileReader> fr(app_factory_->create_file_reader());
if ((err = fr.open(m3u8_)) != srs_success) { if ((err = fr->open(m3u8_)) != srs_success) {
return srs_error_wrap(err, "open file"); return srs_error_wrap(err, "open file");
} }
std::string body; std::string body;
if ((err = srs_io_readall(&fr, body)) != srs_success) { if ((err = srs_io_readall(fr.get(), body)) != srs_success) {
return srs_error_wrap(err, "read data"); return srs_error_wrap(err, "read data");
} }
if (body.empty()) { if (body.empty()) {
@ -1745,13 +1789,13 @@ srs_error_t SrsHlsMuxer::write_hls_key()
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(current_->sequence_no_)); key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(current_->sequence_no_));
string key_url = hls_key_file_path_ + "/" + key_file; string key_url = hls_key_file_path_ + "/" + key_file;
SrsFileWriter fw; SrsUniquePtr<ISrsFileWriter> fw(app_factory_->create_file_writer());
if ((err = fw.open(key_url)) != srs_success) { if ((err = fw->open(key_url)) != srs_success) {
return srs_error_wrap(err, "open file %s", key_url.c_str()); return srs_error_wrap(err, "open file %s", key_url.c_str());
} }
err = fw.write(key_, 16, NULL); err = fw->write(key_, 16, NULL);
fw.close(); fw->close();
if (err != srs_success) { if (err != srs_success) {
return srs_error_wrap(err, "write key"); return srs_error_wrap(err, "write key");
@ -1775,23 +1819,25 @@ srs_error_t SrsHlsMuxer::refresh_m3u8()
} }
std::string temp_m3u8 = m3u8_ + ".temp"; std::string temp_m3u8 = m3u8_ + ".temp";
if ((err = _refresh_m3u8(temp_m3u8)) == srs_success) { if ((err = do_refresh_m3u8(temp_m3u8)) == srs_success) {
if (rename(temp_m3u8.c_str(), m3u8_.c_str()) < 0) { if (rename(temp_m3u8.c_str(), m3u8_.c_str()) < 0) {
err = srs_error_new(ERROR_HLS_WRITE_FAILED, "hls: rename m3u8 file failed. %s => %s", temp_m3u8.c_str(), m3u8_.c_str()); err = srs_error_new(ERROR_HLS_WRITE_FAILED, "hls: rename m3u8 file failed. %s => %s", temp_m3u8.c_str(), m3u8_.c_str());
} }
} }
// remove the temp file. // remove the temp file.
if (srs_path_exists(temp_m3u8)) { SrsUniquePtr<SrsPath> path(app_factory_->create_path());
if (unlink(temp_m3u8.c_str()) < 0) { if (path->exists(temp_m3u8)) {
srs_warn("ignore remove m3u8 failed, %s", temp_m3u8.c_str()); if ((err = path->unlink(temp_m3u8)) != srs_success) {
srs_warn("refresh: ignore remove m3u8 failed, %s", srs_error_desc(err).c_str());
srs_freep(err);
} }
} }
return err; return err;
} }
srs_error_t SrsHlsMuxer::_refresh_m3u8(string m3u8_file) srs_error_t SrsHlsMuxer::do_refresh_m3u8(string m3u8_file)
{ {
srs_error_t err = srs_success; srs_error_t err = srs_success;
@ -1800,8 +1846,8 @@ srs_error_t SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
return err; return err;
} }
SrsFileWriter writer; SrsUniquePtr<ISrsFileWriter> writer(app_factory_->create_file_writer());
if ((err = writer.open(m3u8_file)) != srs_success) { if ((err = writer->open(m3u8_file)) != srs_success) {
return srs_error_wrap(err, "hls: open m3u8 file %s", m3u8_file.c_str()); return srs_error_wrap(err, "hls: open m3u8 file %s", m3u8_file.c_str());
} }
@ -1882,7 +1928,7 @@ srs_error_t SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
// write m3u8 to writer. // write m3u8 to writer.
std::string m3u8 = ss.str(); std::string m3u8 = ss.str();
if ((err = writer.write((char *)m3u8.c_str(), (int)m3u8.length(), NULL)) != srs_success) { if ((err = writer->write((char *)m3u8.c_str(), (int)m3u8.length(), NULL)) != srs_success) {
return srs_error_wrap(err, "hls: write m3u8"); return srs_error_wrap(err, "hls: write m3u8");
} }
@ -1905,12 +1951,16 @@ SrsHlsController::SrsHlsController()
hls_dts_directly_ = false; hls_dts_directly_ = false;
previous_audio_dts_ = 0; previous_audio_dts_ = 0;
aac_samples_ = 0; aac_samples_ = 0;
config_ = _srs_config;
} }
SrsHlsController::~SrsHlsController() SrsHlsController::~SrsHlsController()
{ {
srs_freep(muxer_); srs_freep(muxer_);
srs_freep(tsmc_); srs_freep(tsmc_);
config_ = NULL;
} }
// CRITICAL: This method is called AFTER the source has been added to the source pool // CRITICAL: This method is called AFTER the source has been added to the source pool
@ -1961,34 +2011,34 @@ srs_error_t SrsHlsController::on_publish(ISrsRequest *req)
std::string stream = req->stream_; std::string stream = req->stream_;
std::string app = req->app_; std::string app = req->app_;
srs_utime_t hls_fragment = _srs_config->get_hls_fragment(vhost); srs_utime_t hls_fragment = config_->get_hls_fragment(vhost);
double hls_td_ratio = _srs_config->get_hls_td_ratio(vhost); double hls_td_ratio = config_->get_hls_td_ratio(vhost);
srs_utime_t hls_window = _srs_config->get_hls_window(vhost); srs_utime_t hls_window = config_->get_hls_window(vhost);
// get the hls m3u8 ts list entry prefix config // get the hls m3u8 ts list entry prefix config
std::string entry_prefix = _srs_config->get_hls_entry_prefix(vhost); std::string entry_prefix = config_->get_hls_entry_prefix(vhost);
// get the hls path config // get the hls path config
std::string path = _srs_config->get_hls_path(vhost); std::string path = config_->get_hls_path(vhost);
std::string m3u8_file = _srs_config->get_hls_m3u8_file(vhost); std::string m3u8_file = config_->get_hls_m3u8_file(vhost);
std::string ts_file = _srs_config->get_hls_ts_file(vhost); std::string ts_file = config_->get_hls_ts_file(vhost);
bool cleanup = _srs_config->get_hls_cleanup(vhost); bool cleanup = config_->get_hls_cleanup(vhost);
bool wait_keyframe = _srs_config->get_hls_wait_keyframe(vhost); bool wait_keyframe = config_->get_hls_wait_keyframe(vhost);
// the audio overflow, for pure audio to reap segment. // the audio overflow, for pure audio to reap segment.
double hls_aof_ratio = _srs_config->get_hls_aof_ratio(vhost); double hls_aof_ratio = config_->get_hls_aof_ratio(vhost);
// whether use floor(timestamp/hls_fragment) for variable timestamp // whether use floor(timestamp/hls_fragment) for variable timestamp
bool ts_floor = _srs_config->get_hls_ts_floor(vhost); bool ts_floor = config_->get_hls_ts_floor(vhost);
// the seconds to dispose the hls. // the seconds to dispose the hls.
srs_utime_t hls_dispose = _srs_config->get_hls_dispose(vhost); srs_utime_t hls_dispose = config_->get_hls_dispose(vhost);
bool hls_keys = _srs_config->get_hls_keys(vhost); bool hls_keys = config_->get_hls_keys(vhost);
int hls_fragments_per_key = _srs_config->get_hls_fragments_per_key(vhost); int hls_fragments_per_key = config_->get_hls_fragments_per_key(vhost);
string hls_key_file = _srs_config->get_hls_key_file(vhost); string hls_key_file = config_->get_hls_key_file(vhost);
string hls_key_file_path = _srs_config->get_hls_key_file_path(vhost); string hls_key_file_path = config_->get_hls_key_file_path(vhost);
string hls_key_url = _srs_config->get_hls_key_url(vhost); string hls_key_url = config_->get_hls_key_url(vhost);
// TODO: FIXME: support load exists m3u8, to recover publish stream. // TODO: FIXME: support load exists m3u8, to recover publish stream.
// for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase. // for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase.
bool recover = _srs_config->get_hls_recover(vhost); bool recover = config_->get_hls_recover(vhost);
if ((err = muxer_->on_publish(req)) != srs_success) { if ((err = muxer_->on_publish(req)) != srs_success) {
return srs_error_wrap(err, "muxer publish"); return srs_error_wrap(err, "muxer publish");
@ -2011,7 +2061,7 @@ srs_error_t SrsHlsController::on_publish(ISrsRequest *req)
// This config item is used in SrsHls, we just log its value here. // This config item is used in SrsHls, we just log its value here.
// If enabled, directly turn FLV timestamp to TS DTS. // If enabled, directly turn FLV timestamp to TS DTS.
// @remark It'll be reloaded automatically, because the origin hub will republish while reloading. // @remark It'll be reloaded automatically, because the origin hub will republish while reloading.
hls_dts_directly_ = _srs_config->get_vhost_hls_dts_directly(req->vhost_); hls_dts_directly_ = config_->get_vhost_hls_dts_directly(req->vhost_);
srs_trace("hls: win=%dms, frag=%dms, prefix=%s, path=%s, m3u8=%s, ts=%s, tdr=%.2f, aof=%.2f, floor=%d, clean=%d, waitk=%d, dispose=%dms, dts_directly=%d", srs_trace("hls: win=%dms, frag=%dms, prefix=%s, path=%s, m3u8=%s, ts=%s, tdr=%.2f, aof=%.2f, floor=%d, clean=%d, waitk=%d, dispose=%dms, dts_directly=%d",
srsu2msi(hls_window), srsu2msi(hls_fragment), entry_prefix.c_str(), path.c_str(), m3u8_file.c_str(), ts_file.c_str(), srsu2msi(hls_window), srsu2msi(hls_fragment), entry_prefix.c_str(), path.c_str(), m3u8_file.c_str(), ts_file.c_str(),
@ -2235,11 +2285,15 @@ SrsHlsMp4Controller::SrsHlsMp4Controller()
req_ = NULL; req_ = NULL;
muxer_ = new SrsHlsFmp4Muxer(); muxer_ = new SrsHlsFmp4Muxer();
config_ = _srs_config;
} }
SrsHlsMp4Controller::~SrsHlsMp4Controller() SrsHlsMp4Controller::~SrsHlsMp4Controller()
{ {
srs_freep(muxer_); srs_freep(muxer_);
config_ = NULL;
} }
// CRITICAL: This method is called AFTER the source has been added to the source pool // CRITICAL: This method is called AFTER the source has been added to the source pool
@ -2273,11 +2327,11 @@ srs_error_t SrsHlsMp4Controller::on_publish(ISrsRequest *req)
std::string app = req->app_; std::string app = req->app_;
// get the hls m3u8 ts list entry prefix config // get the hls m3u8 ts list entry prefix config
std::string entry_prefix = _srs_config->get_hls_entry_prefix(vhost); std::string entry_prefix = config_->get_hls_entry_prefix(vhost);
// get the hls path config // get the hls path config
std::string path = _srs_config->get_hls_path(vhost); std::string path = config_->get_hls_path(vhost);
std::string m3u8_file = _srs_config->get_hls_m3u8_file(vhost); std::string m3u8_file = config_->get_hls_m3u8_file(vhost);
std::string ts_file = _srs_config->get_hls_ts_file(vhost); std::string ts_file = config_->get_hls_ts_file(vhost);
if ((err = muxer_->on_publish(req)) != srs_success) { if ((err = muxer_->on_publish(req)) != srs_success) {
return srs_error_wrap(err, "muxer publish"); return srs_error_wrap(err, "muxer publish");
@ -2417,6 +2471,8 @@ SrsHls::SrsHls()
controller_ = NULL; controller_ = NULL;
pprint_ = SrsPithyPrint::create_hls(); pprint_ = SrsPithyPrint::create_hls();
config_ = _srs_config;
} }
SrsHls::~SrsHls() SrsHls::~SrsHls()
@ -2424,6 +2480,8 @@ SrsHls::~SrsHls()
srs_freep(jitter_); srs_freep(jitter_);
srs_freep(controller_); srs_freep(controller_);
srs_freep(pprint_); srs_freep(pprint_);
config_ = NULL;
} }
void SrsHls::async_reload() void SrsHls::async_reload()
@ -2483,7 +2541,7 @@ void SrsHls::dispose()
// Ignore when hls_dispose disabled. // Ignore when hls_dispose disabled.
// @see https://github.com/ossrs/srs/issues/865 // @see https://github.com/ossrs/srs/issues/865
srs_utime_t hls_dispose = _srs_config->get_hls_dispose(req_->vhost_); srs_utime_t hls_dispose = config_->get_hls_dispose(req_->vhost_);
if (!hls_dispose) { if (!hls_dispose) {
return; return;
} }
@ -2512,7 +2570,7 @@ srs_error_t SrsHls::cycle()
return err; return err;
// If not unpublishing and not reloading, try to dispose HLS stream. // If not unpublishing and not reloading, try to dispose HLS stream.
srs_utime_t hls_dispose = _srs_config->get_hls_dispose(req_->vhost_); srs_utime_t hls_dispose = config_->get_hls_dispose(req_->vhost_);
if (hls_dispose <= 0) { if (hls_dispose <= 0) {
return err; return err;
} }
@ -2535,7 +2593,7 @@ srs_error_t SrsHls::cycle()
srs_utime_t SrsHls::cleanup_delay() srs_utime_t SrsHls::cleanup_delay()
{ {
// We use larger timeout to cleanup the HLS, after disposed it if required. // We use larger timeout to cleanup the HLS, after disposed it if required.
return _srs_config->get_hls_dispose(req_->vhost_) * 1.1; return config_->get_hls_dispose(req_->vhost_) * 1.1;
} }
// CRITICAL: This method is called AFTER the source has been added to the source pool // CRITICAL: This method is called AFTER the source has been added to the source pool
@ -2551,7 +2609,7 @@ srs_error_t SrsHls::initialize(SrsOriginHub *h, ISrsRequest *r)
hub_ = h; hub_ = h;
req_ = r; req_ = r;
bool is_fmp4_enabled = _srs_config->get_hls_use_fmp4(r->vhost_); bool is_fmp4_enabled = config_->get_hls_use_fmp4(r->vhost_);
if (!controller_) { if (!controller_) {
if (is_fmp4_enabled) { if (is_fmp4_enabled) {
@ -2580,7 +2638,7 @@ srs_error_t SrsHls::on_publish()
return err; return err;
} }
if (!_srs_config->get_hls_enabled(req_->vhost_)) { if (!config_->get_hls_enabled(req_->vhost_)) {
return err; return err;
} }

View File

@ -27,13 +27,18 @@ class ISrsRequest;
class SrsPithyPrint; class SrsPithyPrint;
class SrsLiveSource; class SrsLiveSource;
class SrsOriginHub; class SrsOriginHub;
class SrsFileWriter; class ISrsFileWriter;
class ISrsAppConfig;
class ISrsHttpHooks;
class SrsSimpleStream; class SrsSimpleStream;
class SrsTsAacJitter; class SrsTsAacJitter;
class SrsTsMessageCache; class SrsTsMessageCache;
class SrsHlsSegment; class SrsHlsSegment;
class SrsTsContext; class SrsTsContext;
class SrsFmp4SegmentEncoder; class SrsFmp4SegmentEncoder;
class ISrsHttpHooks;
class ISrsAppConfig;
class SrsAppFactory;
// The wrapper of m3u8 segment from specification: // The wrapper of m3u8 segment from specification:
// //
@ -48,7 +53,7 @@ public:
// ts uri in m3u8. // ts uri in m3u8.
std::string uri_; std::string uri_;
// The underlayer file writer. // The underlayer file writer.
SrsFileWriter *writer_; ISrsFileWriter *writer_;
// The TS context writer to write TS to file. // The TS context writer to write TS to file.
SrsTsContextWriter *tscw_; SrsTsContextWriter *tscw_;
// Will be saved in m3u8 file. // Will be saved in m3u8 file.
@ -57,7 +62,7 @@ public:
std::string keypath_; std::string keypath_;
public: public:
SrsHlsSegment(SrsTsContext *c, SrsAudioCodecId ac, SrsVideoCodecId vc, SrsFileWriter *w); SrsHlsSegment(SrsTsContext *c, SrsAudioCodecId ac, SrsVideoCodecId vc, ISrsFileWriter *w);
virtual ~SrsHlsSegment(); virtual ~SrsHlsSegment();
public: public:
@ -69,7 +74,7 @@ public:
class SrsInitMp4Segment : public SrsFragment class SrsInitMp4Segment : public SrsFragment
{ {
private: private:
SrsFileWriter *fw_; ISrsFileWriter *fw_;
SrsMp4M2tsInitEncoder init_; SrsMp4M2tsInitEncoder init_;
private: private:
@ -81,7 +86,7 @@ private:
uint8_t const_iv_size_; uint8_t const_iv_size_;
public: public:
SrsInitMp4Segment(SrsFileWriter *fw); SrsInitMp4Segment(ISrsFileWriter *fw);
virtual ~SrsInitMp4Segment(); virtual ~SrsInitMp4Segment();
public: public:
@ -99,7 +104,7 @@ private:
class SrsHlsM4sSegment : public SrsFragment class SrsHlsM4sSegment : public SrsFragment
{ {
private: private:
SrsFileWriter *fw_; ISrsFileWriter *fw_;
SrsFmp4SegmentEncoder enc_; SrsFmp4SegmentEncoder enc_;
public: public:
@ -111,7 +116,7 @@ public:
unsigned char iv_[16]; unsigned char iv_[16];
public: public:
SrsHlsM4sSegment(SrsFileWriter *fw); SrsHlsM4sSegment(ISrsFileWriter *fw);
virtual ~SrsHlsM4sSegment(); virtual ~SrsHlsM4sSegment();
public: public:
@ -125,6 +130,10 @@ public:
// The hls async call: on_hls // The hls async call: on_hls
class SrsDvrAsyncCallOnHls : public ISrsAsyncCallTask class SrsDvrAsyncCallOnHls : public ISrsAsyncCallTask
{ {
private:
ISrsAppConfig *config_;
ISrsHttpHooks *hooks_;
private: private:
SrsContextId cid_; SrsContextId cid_;
std::string path_; std::string path_;
@ -148,6 +157,10 @@ public:
// The hls async call: on_hls_notify // The hls async call: on_hls_notify
class SrsDvrAsyncCallOnHlsNotify : public ISrsAsyncCallTask class SrsDvrAsyncCallOnHlsNotify : public ISrsAsyncCallTask
{ {
private:
ISrsAppConfig *config_;
ISrsHttpHooks *hooks_;
private: private:
SrsContextId cid_; SrsContextId cid_;
std::string ts_url_; std::string ts_url_;
@ -171,6 +184,10 @@ public:
// TODO: Rename to SrsHlsTsMuxer, for TS file only. // TODO: Rename to SrsHlsTsMuxer, for TS file only.
class SrsHlsMuxer class SrsHlsMuxer
{ {
private:
ISrsAppConfig *config_;
SrsAppFactory *app_factory_;
private: private:
ISrsRequest *req_; ISrsRequest *req_;
@ -212,7 +229,7 @@ private:
unsigned char key_[16]; unsigned char key_[16];
unsigned char iv_[16]; unsigned char iv_[16];
// The underlayer file writer. // The underlayer file writer.
SrsFileWriter *writer_; ISrsFileWriter *writer_;
private: private:
int sequence_no_; int sequence_no_;
@ -293,13 +310,15 @@ private:
virtual srs_error_t do_segment_close(); virtual srs_error_t do_segment_close();
virtual srs_error_t write_hls_key(); virtual srs_error_t write_hls_key();
virtual srs_error_t refresh_m3u8(); virtual srs_error_t refresh_m3u8();
virtual srs_error_t _refresh_m3u8(std::string m3u8_file); virtual srs_error_t do_refresh_m3u8(std::string m3u8_file);
// Check if a segment with the given URI already exists in the segments list. // Check if a segment with the given URI already exists in the segments list.
virtual bool segment_exists(const std::string &ts_url); virtual bool segment_exists(const std::string &ts_url);
public: public:
// HLS recover mode. // HLS recover mode.
srs_error_t recover_hls(); srs_error_t recover_hls();
private:
virtual srs_error_t do_recover_hls();
}; };
// Mux the HLS stream(m3u8 and m4s files). // Mux the HLS stream(m3u8 and m4s files).
@ -307,6 +326,10 @@ public:
// to flush video/audio, without any mechenisms. // to flush video/audio, without any mechenisms.
class SrsHlsFmp4Muxer class SrsHlsFmp4Muxer
{ {
private:
ISrsAppConfig *config_;
SrsAppFactory *app_factory_;
private: private:
ISrsRequest *req_; ISrsRequest *req_;
@ -359,7 +382,7 @@ private:
unsigned char kid_[16]; unsigned char kid_[16];
unsigned char iv_[16]; unsigned char iv_[16];
// The underlayer file writer. // The underlayer file writer.
SrsFileWriter *writer_; ISrsFileWriter *writer_;
private: private:
int sequence_no_; int sequence_no_;
@ -441,7 +464,7 @@ private:
virtual srs_error_t do_segment_close(); virtual srs_error_t do_segment_close();
virtual srs_error_t write_hls_key(); virtual srs_error_t write_hls_key();
virtual srs_error_t refresh_m3u8(); virtual srs_error_t refresh_m3u8();
virtual srs_error_t _refresh_m3u8(std::string m3u8_file); virtual srs_error_t do_refresh_m3u8(std::string m3u8_file);
}; };
// The base class for HLS controller // The base class for HLS controller
@ -489,6 +512,9 @@ public:
// TODO: Rename to SrsHlsTsController, for TS file only. // TODO: Rename to SrsHlsTsController, for TS file only.
class SrsHlsController : public ISrsHlsController class SrsHlsController : public ISrsHlsController
{ {
private:
ISrsAppConfig *config_;
private: private:
// The HLS muxer to reap ts and m3u8. // The HLS muxer to reap ts and m3u8.
// The TS is cached to SrsTsMessageCache then flush to ts segment. // The TS is cached to SrsTsMessageCache then flush to ts segment.
@ -542,6 +568,9 @@ private:
// Direct sample processing without caching, simpler than TS controller. // Direct sample processing without caching, simpler than TS controller.
class SrsHlsMp4Controller : public ISrsHlsController class SrsHlsMp4Controller : public ISrsHlsController
{ {
private:
ISrsAppConfig *config_;
private: private:
bool has_video_sh_; bool has_video_sh_;
bool has_audio_sh_; bool has_audio_sh_;
@ -587,6 +616,9 @@ public:
// TODO: FIXME: add utest for hls. // TODO: FIXME: add utest for hls.
class SrsHls class SrsHls
{ {
private:
ISrsAppConfig *config_;
private: private:
ISrsHlsController *controller_; ISrsHlsController *controller_;

View File

@ -110,7 +110,8 @@
XX(ERROR_NO_SOURCE, 1097, "NoSource", "No source found") \ XX(ERROR_NO_SOURCE, 1097, "NoSource", "No source found") \
XX(ERROR_STREAM_DISPOSING, 1098, "StreamDisposing", "Stream is disposing") \ XX(ERROR_STREAM_DISPOSING, 1098, "StreamDisposing", "Stream is disposing") \
XX(ERROR_NOT_IMPLEMENTED, 1099, "NotImplemented", "Feature is not implemented") \ 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")
/**************************************************/ /**************************************************/
/* RTMP protocol error. */ /* RTMP protocol error. */

View File

@ -32,6 +32,14 @@ srs_fclose_t _srs_fclose_fn = ::fclose;
srs_ftell_t _srs_ftell_fn = ::ftell; srs_ftell_t _srs_ftell_fn = ::ftell;
srs_setvbuf_t _srs_setvbuf_fn = ::setvbuf; srs_setvbuf_t _srs_setvbuf_fn = ::setvbuf;
ISrsFileWriter::ISrsFileWriter()
{
}
ISrsFileWriter::~ISrsFileWriter()
{
}
SrsFileWriter::SrsFileWriter() SrsFileWriter::SrsFileWriter()
{ {
fp_ = NULL; fp_ = NULL;
@ -199,6 +207,14 @@ SrsFileReader *ISrsFileReaderFactory::create_file_reader()
return new SrsFileReader(); return new SrsFileReader();
} }
ISrsFileReader::ISrsFileReader()
{
}
ISrsFileReader::~ISrsFileReader()
{
}
SrsFileReader::SrsFileReader() SrsFileReader::SrsFileReader()
{ {
fd_ = -1; fd_ = -1;

View File

@ -19,10 +19,20 @@
class SrsFileReader; class SrsFileReader;
/** // The file writer factory.
* file writer, to write to file. class ISrsFileWriter : public ISrsWriteSeeker
*/ {
class SrsFileWriter : public ISrsWriteSeeker public:
ISrsFileWriter();
virtual ~ISrsFileWriter();
public:
virtual srs_error_t open(std::string p) = 0;
virtual void close() = 0;
};
// file writer, to write to file.
class SrsFileWriter : public ISrsFileWriter
{ {
private: private:
std::string path_; std::string path_;
@ -34,25 +44,17 @@ public:
virtual ~SrsFileWriter(); virtual ~SrsFileWriter();
public: public:
/** // set io buf size
* set io buf size
*/
virtual srs_error_t set_iobuf_size(int size); virtual srs_error_t set_iobuf_size(int size);
/** // open file writer, in truncate mode.
* open file writer, in truncate mode. // @param p a string indicates the path of file to open.
* @param p a string indicates the path of file to open.
*/
virtual srs_error_t open(std::string p); virtual srs_error_t open(std::string p);
/** // open file writer, in append mode.
* open file writer, in append mode. // @param p a string indicates the path of file to open.
* @param p a string indicates the path of file to open.
*/
virtual srs_error_t open_append(std::string p); virtual srs_error_t open_append(std::string p);
/** // close current writer.
* close current writer. // @remark user can reopen again.
* @remark user can reopen again.
*/
virtual void close(); virtual void close();
public: public:
@ -77,10 +79,22 @@ public:
virtual SrsFileReader *create_file_reader(); virtual SrsFileReader *create_file_reader();
}; };
// The file reader.
class ISrsFileReader : public ISrsReadSeeker
{
public:
ISrsFileReader();
virtual ~ISrsFileReader();
public:
virtual srs_error_t open(std::string p) = 0;
virtual void close() = 0;
};
/** /**
* file reader, to read from file. * file reader, to read from file.
*/ */
class SrsFileReader : public ISrsReadSeeker class SrsFileReader : public ISrsFileReader
{ {
private: private:
std::string path_; std::string path_;

View File

@ -527,6 +527,28 @@ srs_error_t srs_os_mkdir_all(string dir)
return srs_error_new(ret, "create dir %s", dir.c_str()); return srs_error_new(ret, "create dir %s", dir.c_str());
} }
SrsPath::SrsPath()
{
}
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) bool srs_path_exists(std::string path)
{ {
struct stat st; struct stat st;

View File

@ -129,6 +129,20 @@ extern bool srs_bytes_equal(void *pa, void *pb, int size);
// Create dir recursively // Create dir recursively
extern srs_error_t srs_os_mkdir_all(std::string dir); extern srs_error_t srs_os_mkdir_all(std::string dir);
// The path utility.
class SrsPath
{
public:
SrsPath();
virtual ~SrsPath();
public:
// Whether path exists.
virtual bool exists(std::string path);
// Remove or unlink file.
virtual srs_error_t unlink(std::string path);
};
// Whether path exists. // Whether path exists.
extern bool srs_path_exists(std::string path); extern bool srs_path_exists(std::string path);
// Get the dirname of path, for instance, dirname("/live/livestream")="/live" // Get the dirname of path, for instance, dirname("/live/livestream")="/live"

View File

@ -61,6 +61,7 @@ SrsConfig *_srs_config = NULL;
// @global kernel factory. // @global kernel factory.
ISrsKernelFactory *_srs_kernel_factory = new SrsFinalFactory(); ISrsKernelFactory *_srs_kernel_factory = new SrsFinalFactory();
SrsAppFactory *_srs_app_factory = new SrsAppFactory();
// @global version of srs, which can grep keyword "XCORE" // @global version of srs, which can grep keyword "XCORE"
extern const char *_srs_version; extern const char *_srs_version;

View File

@ -51,6 +51,7 @@ bool _srs_config_by_env = false;
// @global kernel factory. // @global kernel factory.
ISrsKernelFactory *_srs_kernel_factory = new SrsFinalFactory(); ISrsKernelFactory *_srs_kernel_factory = new SrsFinalFactory();
SrsAppFactory *_srs_app_factory = new SrsAppFactory();
// The binary name of SRS. // The binary name of SRS.
const char *_srs_binary = NULL; const char *_srs_binary = NULL;

View File

@ -2240,6 +2240,156 @@ bool MockAppConfig::get_rtc_stun_strict_check(std::string vhost)
return false; // Default to non-strict mode return false; // Default to non-strict mode
} }
SrsConfDirective *MockAppConfig::get_vhost_on_hls(std::string vhost)
{
return NULL;
}
SrsConfDirective *MockAppConfig::get_vhost_on_hls_notify(std::string vhost)
{
return NULL;
}
bool MockAppConfig::get_hls_enabled(std::string vhost)
{
return false;
}
bool MockAppConfig::get_hls_enabled(SrsConfDirective *vhost)
{
return false;
}
bool MockAppConfig::get_hls_use_fmp4(std::string vhost)
{
return false;
}
std::string MockAppConfig::get_hls_entry_prefix(std::string vhost)
{
return "";
}
std::string MockAppConfig::get_hls_path(std::string vhost)
{
return "./objs/nginx/html";
}
std::string MockAppConfig::get_hls_m3u8_file(std::string vhost)
{
return "[app]/[stream].m3u8";
}
std::string MockAppConfig::get_hls_ts_file(std::string vhost)
{
return "[app]/[stream]-[seq].ts";
}
std::string MockAppConfig::get_hls_fmp4_file(std::string vhost)
{
return "[app]/[stream]-[seq].m4s";
}
std::string MockAppConfig::get_hls_init_file(std::string vhost)
{
return "[app]/[stream]/init.mp4";
}
bool MockAppConfig::get_hls_ts_floor(std::string vhost)
{
return false;
}
srs_utime_t MockAppConfig::get_hls_fragment(std::string vhost)
{
return 10 * SRS_UTIME_SECONDS;
}
double MockAppConfig::get_hls_td_ratio(std::string vhost)
{
return 1.5;
}
double MockAppConfig::get_hls_aof_ratio(std::string vhost)
{
return 2.0;
}
srs_utime_t MockAppConfig::get_hls_window(std::string vhost)
{
return 60 * SRS_UTIME_SECONDS;
}
std::string MockAppConfig::get_hls_on_error(std::string vhost)
{
return "continue";
}
bool MockAppConfig::get_hls_cleanup(std::string vhost)
{
return true;
}
srs_utime_t MockAppConfig::get_hls_dispose(std::string vhost)
{
return 120 * SRS_UTIME_SECONDS;
}
bool MockAppConfig::get_hls_wait_keyframe(std::string vhost)
{
return true;
}
bool MockAppConfig::get_hls_keys(std::string vhost)
{
return false;
}
int MockAppConfig::get_hls_fragments_per_key(std::string vhost)
{
return 5;
}
std::string MockAppConfig::get_hls_key_file(std::string vhost)
{
return "[app]/[stream]-[seq].key";
}
std::string MockAppConfig::get_hls_key_file_path(std::string vhost)
{
return "./objs/nginx/html";
}
std::string MockAppConfig::get_hls_key_url(std::string vhost)
{
return "";
}
int MockAppConfig::get_vhost_hls_nb_notify(std::string vhost)
{
return 64;
}
bool MockAppConfig::get_vhost_hls_dts_directly(std::string vhost)
{
return true;
}
bool MockAppConfig::get_hls_ctx_enabled(std::string vhost)
{
return true;
}
bool MockAppConfig::get_hls_ts_ctx_enabled(std::string vhost)
{
return true;
}
bool MockAppConfig::get_hls_recover(std::string vhost)
{
return true;
}
void MockAppConfig::set_http_hooks_enabled(bool enabled) void MockAppConfig::set_http_hooks_enabled(bool enabled)
{ {
http_hooks_enabled_ = enabled; http_hooks_enabled_ = enabled;

View File

@ -258,6 +258,37 @@ public:
virtual bool get_rtc_to_rtmp(std::string vhost); virtual bool get_rtc_to_rtmp(std::string vhost);
virtual srs_utime_t get_rtc_stun_timeout(std::string vhost); virtual srs_utime_t get_rtc_stun_timeout(std::string vhost);
virtual bool get_rtc_stun_strict_check(std::string vhost); virtual bool get_rtc_stun_strict_check(std::string vhost);
virtual SrsConfDirective *get_vhost_on_hls(std::string vhost);
virtual SrsConfDirective *get_vhost_on_hls_notify(std::string vhost);
// HLS methods
virtual bool get_hls_enabled(std::string vhost);
virtual bool get_hls_enabled(SrsConfDirective *vhost);
virtual bool get_hls_use_fmp4(std::string vhost);
virtual std::string get_hls_entry_prefix(std::string vhost);
virtual std::string get_hls_path(std::string vhost);
virtual std::string get_hls_m3u8_file(std::string vhost);
virtual std::string get_hls_ts_file(std::string vhost);
virtual std::string get_hls_fmp4_file(std::string vhost);
virtual std::string get_hls_init_file(std::string vhost);
virtual bool get_hls_ts_floor(std::string vhost);
virtual srs_utime_t get_hls_fragment(std::string vhost);
virtual double get_hls_td_ratio(std::string vhost);
virtual double get_hls_aof_ratio(std::string vhost);
virtual srs_utime_t get_hls_window(std::string vhost);
virtual std::string get_hls_on_error(std::string vhost);
virtual bool get_hls_cleanup(std::string vhost);
virtual srs_utime_t get_hls_dispose(std::string vhost);
virtual bool get_hls_wait_keyframe(std::string vhost);
virtual bool get_hls_keys(std::string vhost);
virtual int get_hls_fragments_per_key(std::string vhost);
virtual std::string get_hls_key_file(std::string vhost);
virtual std::string get_hls_key_file_path(std::string vhost);
virtual std::string get_hls_key_url(std::string vhost);
virtual int get_vhost_hls_nb_notify(std::string vhost);
virtual bool get_vhost_hls_dts_directly(std::string vhost);
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);
void set_http_hooks_enabled(bool enabled); void set_http_hooks_enabled(bool enabled);
void set_on_stop_urls(const std::vector<std::string> &urls); void set_on_stop_urls(const std::vector<std::string> &urls);
void clear_on_stop_directive(); void clear_on_stop_directive();

View File

@ -12,8 +12,8 @@ using namespace std;
#include <srs_app_stream_token.hpp> #include <srs_app_stream_token.hpp>
#include <srs_kernel_error.hpp> #include <srs_kernel_error.hpp>
#include <srs_kernel_rtc_rtp.hpp> #include <srs_kernel_rtc_rtp.hpp>
#include <srs_protocol_sdp.hpp>
#include <srs_protocol_rtc_stun.hpp> #include <srs_protocol_rtc_stun.hpp>
#include <srs_protocol_sdp.hpp>
#include <srs_utest_app5.hpp> #include <srs_utest_app5.hpp>
#include <srs_utest_app6.hpp> #include <srs_utest_app6.hpp>
@ -1172,10 +1172,6 @@ ISrsRequest *MockRtcConnectionRequest::as_http()
return copy(); return copy();
} }
VOID TEST(SrsRtcConnectionTest, OnDtlsHandshakeDoneTypicalScenario) VOID TEST(SrsRtcConnectionTest, OnDtlsHandshakeDoneTypicalScenario)
{ {
srs_error_t err; srs_error_t err;
@ -1347,8 +1343,8 @@ VOID TEST(SrsRtcConnectionTest, CreatePublisherTypicalScenario)
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription(); SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
audio_desc->type_ = "audio"; audio_desc->type_ = "audio";
audio_desc->ssrc_ = 0x12345678; audio_desc->ssrc_ = 0x12345678;
audio_desc->fec_ssrc_ = 0x12345679; // Different FEC SSRC audio_desc->fec_ssrc_ = 0x12345679; // Different FEC SSRC
audio_desc->rtx_ssrc_ = 0x1234567A; // Different RTX SSRC audio_desc->rtx_ssrc_ = 0x1234567A; // Different RTX SSRC
audio_desc->id_ = "test-audio-track"; audio_desc->id_ = "test-audio-track";
audio_desc->is_active_ = true; audio_desc->is_active_ = true;
audio_desc->direction_ = "sendrecv"; audio_desc->direction_ = "sendrecv";
@ -1358,8 +1354,8 @@ VOID TEST(SrsRtcConnectionTest, CreatePublisherTypicalScenario)
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription(); SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
video_desc->type_ = "video"; video_desc->type_ = "video";
video_desc->ssrc_ = 0x87654321; video_desc->ssrc_ = 0x87654321;
video_desc->fec_ssrc_ = 0x87654322; // Different FEC SSRC video_desc->fec_ssrc_ = 0x87654322; // Different FEC SSRC
video_desc->rtx_ssrc_ = 0x87654323; // Different RTX SSRC video_desc->rtx_ssrc_ = 0x87654323; // Different RTX SSRC
video_desc->id_ = "test-video-track"; video_desc->id_ = "test-video-track";
video_desc->is_active_ = true; video_desc->is_active_ = true;
video_desc->direction_ = "sendrecv"; video_desc->direction_ = "sendrecv";
@ -1380,7 +1376,7 @@ VOID TEST(SrsRtcConnectionTest, CreatePublisherTypicalScenario)
// Test the early return logic for existing publisher // Test the early return logic for existing publisher
HELPER_EXPECT_SUCCESS(conn->create_publisher(req.get(), stream_desc.get())); HELPER_EXPECT_SUCCESS(conn->create_publisher(req.get(), stream_desc.get()));
EXPECT_EQ(1, (int)conn->publishers_.size()); // Should still be 1 EXPECT_EQ(1, (int)conn->publishers_.size()); // Should still be 1
EXPECT_EQ(existing_publisher, conn->publishers_[req->get_stream_url()]); // Should be the same publisher EXPECT_EQ(existing_publisher, conn->publishers_[req->get_stream_url()]); // Should be the same publisher
// Test scenario 3: Test duplicate SSRC error detection // Test scenario 3: Test duplicate SSRC error detection
@ -1446,7 +1442,7 @@ VOID TEST(SrsRtcConnectionTest, CreatePublisherTypicalScenario)
} }
// Verify all SSRC mappings were created correctly // Verify all SSRC mappings were created correctly
EXPECT_EQ(6, (int)conn->publishers_ssrc_map_.size()); // 3 audio + 3 video SSRCs EXPECT_EQ(6, (int)conn->publishers_ssrc_map_.size()); // 3 audio + 3 video SSRCs
EXPECT_EQ(test_publisher, conn->publishers_ssrc_map_[0x12345678]); // Audio main EXPECT_EQ(test_publisher, conn->publishers_ssrc_map_[0x12345678]); // Audio main
EXPECT_EQ(test_publisher, conn->publishers_ssrc_map_[0x12345679]); // Audio FEC EXPECT_EQ(test_publisher, conn->publishers_ssrc_map_[0x12345679]); // Audio FEC
EXPECT_EQ(test_publisher, conn->publishers_ssrc_map_[0x1234567A]); // Audio RTX EXPECT_EQ(test_publisher, conn->publishers_ssrc_map_[0x1234567A]); // Audio RTX
@ -1647,7 +1643,7 @@ VOID TEST(SrsRtcConnectionTest, SendRtcpRrTypicalScenario)
// Set up test parameters for typical RTCP RR scenario // Set up test parameters for typical RTCP RR scenario
uint32_t test_ssrc = 0x12345678; uint32_t test_ssrc = 0x12345678;
uint64_t last_send_systime = 1000000; // 1 second in microseconds uint64_t last_send_systime = 1000000; // 1 second in microseconds
SrsNtp last_send_ntp = SrsNtp::from_time_ms(1000); // 1 second in milliseconds SrsNtp last_send_ntp = SrsNtp::from_time_ms(1000); // 1 second in milliseconds
// Test typical send_rtcp_rr scenario - should create and send RTCP RR packet // Test typical send_rtcp_rr scenario - should create and send RTCP RR packet
@ -1664,9 +1660,9 @@ VOID TEST(SrsRtcConnectionTest, SendRtcpRrTypicalScenario)
// Verify RTCP header: V=2, P=0, RC=1, PT=201(RR), length=7 // Verify RTCP header: V=2, P=0, RC=1, PT=201(RR), length=7
EXPECT_EQ(0x81, (unsigned char)rtcp_data[0]); // V=2, P=0, RC=1 EXPECT_EQ(0x81, (unsigned char)rtcp_data[0]); // V=2, P=0, RC=1
EXPECT_EQ(201, (unsigned char)rtcp_data[1]); // PT=201 (RR) EXPECT_EQ(201, (unsigned char)rtcp_data[1]); // PT=201 (RR)
EXPECT_EQ(0x00, (unsigned char)rtcp_data[2]); // Length high byte EXPECT_EQ(0x00, (unsigned char)rtcp_data[2]); // Length high byte
EXPECT_EQ(0x07, (unsigned char)rtcp_data[3]); // Length low byte (7 words = 32 bytes) EXPECT_EQ(0x07, (unsigned char)rtcp_data[3]); // Length low byte (7 words = 32 bytes)
} }
// Test error handling scenario // Test error handling scenario
@ -1790,7 +1786,7 @@ VOID TEST(SrsRtcConnectionTest, AddPublisherTypicalScenario)
SrsUniquePtr<MockRtcSourceManager> mock_rtc_sources(new MockRtcSourceManager()); SrsUniquePtr<MockRtcSourceManager> mock_rtc_sources(new MockRtcSourceManager());
// Create a mock RTC source that can publish // Create a mock RTC source that can publish
SrsRtcSource* raw_source = new SrsRtcSource(); SrsRtcSource *raw_source = new SrsRtcSource();
mock_rtc_sources->mock_source_ = SrsSharedPtr<SrsRtcSource>(raw_source); mock_rtc_sources->mock_source_ = SrsSharedPtr<SrsRtcSource>(raw_source);
// Replace the default rtc_sources_ with our mock // Replace the default rtc_sources_ with our mock
@ -1814,13 +1810,13 @@ VOID TEST(SrsRtcConnectionTest, AddPublisherTypicalScenario)
// Create a simple remote SDP that will fail negotiation (testing error handling) // Create a simple remote SDP that will fail negotiation (testing error handling)
ruc->remote_sdp_str_ = "v=0\r\n" ruc->remote_sdp_str_ = "v=0\r\n"
"o=- 123456 654321 IN IP4 127.0.0.1\r\n" "o=- 123456 654321 IN IP4 127.0.0.1\r\n"
"s=-\r\n" "s=-\r\n"
"t=0 0\r\n" "t=0 0\r\n"
"m=video 9 UDP/TLS/RTP/SAVPF 96\r\n" "m=video 9 UDP/TLS/RTP/SAVPF 96\r\n"
"a=rtpmap:96 H264/90000\r\n" "a=rtpmap:96 H264/90000\r\n"
"a=fmtp:96 profile-level-id=42e01f\r\n" "a=fmtp:96 profile-level-id=42e01f\r\n"
"a=sendonly\r\n"; "a=sendonly\r\n";
// Parse the remote SDP // Parse the remote SDP
HELPER_EXPECT_SUCCESS(ruc->remote_sdp_.parse(ruc->remote_sdp_str_)); HELPER_EXPECT_SUCCESS(ruc->remote_sdp_.parse(ruc->remote_sdp_str_));
@ -1868,8 +1864,7 @@ VOID TEST(SrsRtcConnectionTest, OnRtpCipherTypicalScenario)
0x56, 0x78, 0x9A, 0xBC, // timestamp 0x56, 0x78, 0x9A, 0xBC, // timestamp
0x12, 0x34, 0x56, 0x78, // SSRC = 0x12345678 0x12, 0x34, 0x56, 0x78, // SSRC = 0x12345678
// RTP payload (sample data) // RTP payload (sample data)
0x01, 0x02, 0x03, 0x04 0x01, 0x02, 0x03, 0x04};
};
// Add the publish stream to the connection's publishers map // Add the publish stream to the connection's publishers map
string stream_url = "/live/test"; string stream_url = "/live/test";
@ -1980,8 +1975,10 @@ VOID TEST(SrsRtcPublisherNegotiatorTest, TypicalUseScenario)
// Find audio and video media descriptions // Find audio and video media descriptions
bool has_audio = false, has_video = false; bool has_audio = false, has_video = false;
for (size_t i = 0; i < local_sdp.media_descs_.size(); i++) { for (size_t i = 0; i < local_sdp.media_descs_.size(); i++) {
if (local_sdp.media_descs_[i].type_ == "audio") has_audio = true; if (local_sdp.media_descs_[i].type_ == "audio")
if (local_sdp.media_descs_[i].type_ == "video") has_video = true; has_audio = true;
if (local_sdp.media_descs_[i].type_ == "video")
has_video = true;
} }
EXPECT_TRUE(has_audio); EXPECT_TRUE(has_audio);
EXPECT_TRUE(has_video); EXPECT_TRUE(has_video);
@ -2223,8 +2220,7 @@ VOID TEST(SrsRtcConnectionTest, OnRtpPlaintextTypicalScenario)
0x56, 0x78, 0x9A, 0xBC, // timestamp 0x56, 0x78, 0x9A, 0xBC, // timestamp
0x12, 0x34, 0x56, 0x78, // SSRC = 0x12345678 0x12, 0x34, 0x56, 0x78, // SSRC = 0x12345678
// RTP payload (sample data) // RTP payload (sample data)
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
};
// Create video track with matching SSRC for the RTP packet using helper function // Create video track with matching SSRC for the RTP packet using helper function
SrsUniquePtr<SrsRtcTrackDescription> video_desc(create_video_track_description_with_codec("H264", test_ssrc)); SrsUniquePtr<SrsRtcTrackDescription> video_desc(create_video_track_description_with_codec("H264", test_ssrc));
@ -2521,8 +2517,6 @@ VOID TEST(SrsRtcConnectionTest, DoCheckSendNacksTypicalScenario)
conn->publishers_.clear(); conn->publishers_.clear();
} }
VOID TEST(SdpUtilityTest, SrsSDPHasH264ProfilePayloadTypeTypicalScenario) VOID TEST(SdpUtilityTest, SrsSDPHasH264ProfilePayloadTypeTypicalScenario)
{ {
// Test srs_sdp_has_h264_profile with SrsMediaPayloadType - typical scenario // Test srs_sdp_has_h264_profile with SrsMediaPayloadType - typical scenario
@ -2661,7 +2655,7 @@ VOID TEST(SrsRtcPlayerNegotiatorTest, TypicalUseScenario)
SrsUniquePtr<SrsRtcSourceDescription> stream_desc(new SrsRtcSourceDescription()); SrsUniquePtr<SrsRtcSourceDescription> stream_desc(new SrsRtcSourceDescription());
// Create audio track description (managed by stream_desc) // Create audio track description (managed by stream_desc)
SrsRtcTrackDescription* audio_track = new SrsRtcTrackDescription(); SrsRtcTrackDescription *audio_track = new SrsRtcTrackDescription();
audio_track->type_ = "audio"; audio_track->type_ = "audio";
audio_track->id_ = "audio_track_id"; audio_track->id_ = "audio_track_id";
audio_track->ssrc_ = 12345; audio_track->ssrc_ = 12345;
@ -2673,7 +2667,7 @@ VOID TEST(SrsRtcPlayerNegotiatorTest, TypicalUseScenario)
audio_track->media_ = new SrsAudioPayload(111, "opus", 48000, 2); audio_track->media_ = new SrsAudioPayload(111, "opus", 48000, 2);
// Create video track description (managed by stream_desc) // Create video track description (managed by stream_desc)
SrsRtcTrackDescription* video_track = new SrsRtcTrackDescription(); SrsRtcTrackDescription *video_track = new SrsRtcTrackDescription();
video_track->type_ = "video"; video_track->type_ = "video";
video_track->id_ = "video_track_id"; video_track->id_ = "video_track_id";
video_track->ssrc_ = 67890; video_track->ssrc_ = 67890;
@ -2694,9 +2688,9 @@ VOID TEST(SrsRtcPlayerNegotiatorTest, TypicalUseScenario)
mock_request.get(), mock_request.get(),
local_sdp, local_sdp,
stream_desc.get(), stream_desc.get(),
true, // unified_plan true, // unified_plan
true // audio_before_video true // audio_before_video
)); ));
// Verify the generated local SDP has the expected structure // Verify the generated local SDP has the expected structure
EXPECT_EQ(2, (int)local_sdp.media_descs_.size()); // Should have audio and video EXPECT_EQ(2, (int)local_sdp.media_descs_.size()); // Should have audio and video

View File

@ -15,10 +15,10 @@
#include <srs_app_rtc_conn.hpp> #include <srs_app_rtc_conn.hpp>
#include <srs_app_rtc_source.hpp> #include <srs_app_rtc_source.hpp>
#include <srs_kernel_rtc_queue.hpp> #include <srs_kernel_rtc_queue.hpp>
#include <srs_utest_app6.hpp>
#include <srs_utest_app2.hpp> #include <srs_utest_app2.hpp>
#include <srs_utest_service.hpp> #include <srs_utest_app6.hpp>
#include <srs_utest_kernel3.hpp> #include <srs_utest_kernel3.hpp>
#include <srs_utest_service.hpp>
// Mock video recv track for testing check_send_nacks // Mock video recv track for testing check_send_nacks
class MockRtcVideoRecvTrackForNack : public SrsRtcVideoRecvTrack class MockRtcVideoRecvTrackForNack : public SrsRtcVideoRecvTrack
@ -157,6 +157,4 @@ public:
virtual ISrsRequest *as_http(); virtual ISrsRequest *as_http();
}; };
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,192 @@
//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#ifndef SRS_UTEST_APP8_HPP
#define SRS_UTEST_APP8_HPP
#include <srs_utest.hpp>
#include <srs_app_factory.hpp>
#include <srs_app_hls.hpp>
#include <srs_app_rtmp_source.hpp>
#include <srs_utest_app6.hpp>
#include <srs_utest_kernel.hpp>
// Extended mock app config for HLS notify testing
class MockAppConfigForHlsNotify : public MockAppConfig
{
public:
SrsConfDirective *on_hls_notify_directive_;
int hls_nb_notify_;
bool hls_enabled_;
public:
MockAppConfigForHlsNotify();
virtual ~MockAppConfigForHlsNotify();
virtual SrsConfDirective *get_vhost_on_hls_notify(std::string vhost);
virtual int get_vhost_hls_nb_notify(std::string vhost);
virtual bool get_hls_enabled(std::string vhost);
void set_on_hls_notify_urls(const std::vector<std::string> &urls);
void clear_on_hls_notify_directive();
void set_hls_nb_notify(int nb_notify);
void set_hls_enabled(bool enabled);
};
// Extended mock HTTP hooks for HLS notify testing
class MockHttpHooksForHlsNotify : public MockHttpHooks
{
public:
std::vector<std::string> on_hls_notify_urls_;
std::vector<std::string> on_hls_notify_ts_urls_;
std::vector<int> on_hls_notify_nb_notifies_;
int on_hls_notify_count_;
srs_error_t on_hls_notify_error_;
public:
MockHttpHooksForHlsNotify();
virtual ~MockHttpHooksForHlsNotify();
virtual srs_error_t on_hls_notify(SrsContextId cid, std::string url, ISrsRequest *req, std::string ts_url, int nb_notify);
void set_on_hls_notify_error(srs_error_t err);
void reset();
};
// Mock request class for HLS testing
class MockHlsRequest : public ISrsRequest
{
public:
MockHlsRequest(std::string vhost = "__defaultVhost__", std::string app = "live", std::string stream = "test");
virtual ~MockHlsRequest();
virtual ISrsRequest *copy();
virtual std::string get_stream_url();
virtual void update_auth(ISrsRequest *req);
virtual void strip();
virtual ISrsRequest *as_http();
};
// Proxy file writer that wraps MockSrsFileWriter for testing
class MockFileWriterProxy : public ISrsFileWriter
{
public:
MockSrsFileWriter *real_writer_;
public:
MockFileWriterProxy(MockSrsFileWriter *writer);
virtual ~MockFileWriterProxy();
virtual srs_error_t open(std::string file);
virtual srs_error_t open_append(std::string file);
virtual void close();
virtual bool is_open();
virtual void seek2(int64_t offset);
virtual int64_t tellg();
virtual srs_error_t write(void *buf, size_t count, ssize_t *pnwrite);
virtual srs_error_t writev(const iovec *iov, int iovcnt, ssize_t *pnwrite);
virtual srs_error_t lseek(off_t offset, int whence, off_t *seeked);
};
// Mock app factory for HLS testing
// Mock encrypted file writer for HLS encryption testing
class MockEncFileWriter : public SrsEncFileWriter
{
public:
MockEncFileWriter();
virtual ~MockEncFileWriter();
};
class MockAppFactory : public SrsAppFactory
{
public:
MockSrsFileWriter *real_writer_;
MockSrsFile *real_file_;
MockSrsFileReader *real_reader_;
int create_file_writer_count_;
int create_file_reader_count_;
public:
MockAppFactory();
virtual ~MockAppFactory();
virtual ISrsFileWriter *create_file_writer();
virtual ISrsFileReader *create_file_reader();
void reset();
};
// Mock HLS muxer for testing SrsHlsController::reap_segment
class MockHlsMuxer
{
public:
int segment_close_count_;
int segment_open_count_;
int flush_video_count_;
int flush_audio_count_;
srs_error_t segment_close_error_;
srs_error_t segment_open_error_;
srs_error_t flush_video_error_;
srs_error_t flush_audio_error_;
public:
MockHlsMuxer();
virtual ~MockHlsMuxer();
srs_error_t segment_close();
srs_error_t segment_open();
srs_error_t flush_video(SrsTsMessageCache *cache);
srs_error_t flush_audio(SrsTsMessageCache *cache);
void reset();
void set_segment_close_error(srs_error_t err);
void set_segment_open_error(srs_error_t err);
void set_flush_video_error(srs_error_t err);
void set_flush_audio_error(srs_error_t err);
};
// Mock HLS controller for testing SrsHls::reload
class MockHlsController : public ISrsHlsController
{
public:
int initialize_count_;
int dispose_count_;
int on_publish_count_;
int on_unpublish_count_;
int write_audio_count_;
int write_video_count_;
int on_sequence_header_count_;
srs_error_t initialize_error_;
srs_error_t on_publish_error_;
srs_error_t on_unpublish_error_;
public:
MockHlsController();
virtual ~MockHlsController();
virtual srs_error_t initialize();
virtual void dispose();
virtual srs_error_t on_publish(ISrsRequest *req);
virtual srs_error_t on_unpublish();
virtual srs_error_t write_audio(SrsMediaPacket *shared_audio, SrsFormat *format);
virtual srs_error_t write_video(SrsMediaPacket *shared_video, SrsFormat *format);
virtual srs_error_t on_sequence_header(SrsMediaPacket *msg, SrsFormat *format);
virtual int sequence_no();
virtual std::string ts_url();
virtual srs_utime_t duration();
virtual int deviation();
void reset();
void set_initialize_error(srs_error_t err);
void set_on_publish_error(srs_error_t err);
void set_on_unpublish_error(srs_error_t err);
};
// Mock origin hub for testing SrsHls::reload
class MockOriginHub : public SrsOriginHub
{
public:
int on_hls_request_sh_count_;
srs_error_t on_hls_request_sh_error_;
public:
MockOriginHub();
virtual ~MockOriginHub();
virtual srs_error_t on_hls_request_sh();
void reset();
void set_on_hls_request_sh_error(srs_error_t err);
};
#endif