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
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_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_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_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
MODULE_FILES+=("srs_utest_srt")
if [[ $SRS_GB28181 == YES ]]; then

View File

@ -296,6 +296,36 @@ public:
virtual bool get_rtc_to_rtmp(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 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.

View File

@ -8,7 +8,38 @@
#include <srs_app_config.hpp>
#include <srs_app_st.hpp>
#include <srs_kernel_file.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()
{

View File

@ -11,6 +11,26 @@
#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.
class SrsFinalFactory : public ISrsKernelFactory
{

View File

@ -19,6 +19,7 @@ using namespace std;
#include <openssl/rand.h>
#include <srs_app_config.hpp>
#include <srs_app_factory.hpp>
#include <srs_app_http_hooks.hpp>
#include <srs_app_rtmp_source.hpp>
#include <srs_app_utility.hpp>
@ -44,7 +45,7 @@ using namespace std;
// reset the piece id when deviation overflow this.
#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;
writer_ = w;
@ -60,7 +61,8 @@ void SrsHlsSegment::config_cipher(unsigned char *key, unsigned char *iv)
{
memcpy(this->iv_, iv, 16);
SrsEncFileWriter *fw = (SrsEncFileWriter *)writer_;
SrsEncFileWriter *fw = dynamic_cast<SrsEncFileWriter *>(writer_);
srs_assert(fw);
fw->config_cipher(key, iv);
}
@ -75,7 +77,7 @@ srs_error_t SrsHlsSegment::rename()
return SrsFragment::rename();
}
SrsInitMp4Segment::SrsInitMp4Segment(SrsFileWriter *fw)
SrsInitMp4Segment::SrsInitMp4Segment(ISrsFileWriter *fw)
{
fw_ = fw;
const_iv_size_ = 0;
@ -165,7 +167,7 @@ srs_error_t SrsInitMp4Segment::init_encoder()
return err;
}
SrsHlsM4sSegment::SrsHlsM4sSegment(SrsFileWriter *fw)
SrsHlsM4sSegment::SrsHlsM4sSegment(ISrsFileWriter *fw)
{
fw_ = fw;
}
@ -264,18 +266,24 @@ SrsDvrAsyncCallOnHls::SrsDvrAsyncCallOnHls(SrsContextId c, ISrsRequest *r, strin
m3u8_url_ = mu;
seq_no_ = s;
duration_ = d;
config_ = _srs_config;
hooks_ = _srs_hooks;
}
SrsDvrAsyncCallOnHls::~SrsDvrAsyncCallOnHls()
{
srs_freep(req_);
config_ = NULL;
hooks_ = NULL;
}
srs_error_t SrsDvrAsyncCallOnHls::call()
{
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;
}
@ -285,7 +293,7 @@ srs_error_t SrsDvrAsyncCallOnHls::call()
vector<string> hooks;
if (true) {
SrsConfDirective *conf = _srs_config->get_vhost_on_hls(req_->vhost_);
SrsConfDirective *conf = config_->get_vhost_on_hls(req_->vhost_);
if (!conf) {
return err;
@ -296,7 +304,7 @@ srs_error_t SrsDvrAsyncCallOnHls::call()
for (int i = 0; i < (int)hooks.size(); 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());
}
}
@ -314,18 +322,24 @@ SrsDvrAsyncCallOnHlsNotify::SrsDvrAsyncCallOnHlsNotify(SrsContextId c, ISrsReque
cid_ = c;
req_ = r->copy();
ts_url_ = u;
config_ = _srs_config;
hooks_ = _srs_hooks;
}
SrsDvrAsyncCallOnHlsNotify::~SrsDvrAsyncCallOnHlsNotify()
{
srs_freep(req_);
config_ = NULL;
hooks_ = NULL;
}
srs_error_t SrsDvrAsyncCallOnHlsNotify::call()
{
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;
}
@ -335,7 +349,7 @@ srs_error_t SrsDvrAsyncCallOnHlsNotify::call()
vector<string> hooks;
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) {
return err;
@ -344,10 +358,10 @@ srs_error_t SrsDvrAsyncCallOnHlsNotify::call()
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++) {
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());
}
}
@ -388,6 +402,9 @@ SrsHlsFmp4Muxer::SrsHlsFmp4Muxer()
memset(key_, 0, 16);
memset(iv_, 0, 16);
config_ = _srs_config;
app_factory_ = _srs_app_factory;
}
SrsHlsFmp4Muxer::~SrsHlsFmp4Muxer()
@ -397,6 +414,9 @@ SrsHlsFmp4Muxer::~SrsHlsFmp4Muxer()
srs_freep(req_);
srs_freep(async_);
srs_freep(writer_);
config_ = NULL;
app_factory_ = NULL;
}
void SrsHlsFmp4Muxer::dispose()
@ -413,8 +433,12 @@ void SrsHlsFmp4Muxer::dispose()
srs_freep(current_);
}
if (unlink(m3u8_.c_str()) < 0) {
srs_warn("dispose unlink path failed. file=%s", m3u8_.c_str());
SrsUniquePtr<SrsPath> path(app_factory_->create_path());
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() : "");
@ -499,10 +523,10 @@ srs_error_t SrsHlsFmp4Muxer::write_init_mp4(SrsFormat *format, bool has_video, b
std::string app = req_->app_;
// 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);
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;
// 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 app = req_->app_;
hls_fragment_ = _srs_config->get_hls_fragment(vhost);
double hls_td_ratio = _srs_config->get_hls_td_ratio(vhost);
hls_window_ = _srs_config->get_hls_window(vhost);
hls_fragment_ = config_->get_hls_fragment(vhost);
double hls_td_ratio = config_->get_hls_td_ratio(vhost);
hls_window_ = config_->get_hls_window(vhost);
// 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
hls_path_ = _srs_config->get_hls_path(vhost);
m3u8_url_ = _srs_config->get_hls_m3u8_file(vhost);
hls_m4s_file_ = _srs_config->get_hls_fmp4_file(vhost);
hls_cleanup_ = _srs_config->get_hls_cleanup(vhost);
hls_wait_keyframe_ = _srs_config->get_hls_wait_keyframe(vhost);
hls_path_ = config_->get_hls_path(vhost);
m3u8_url_ = config_->get_hls_m3u8_file(vhost);
hls_m4s_file_ = config_->get_hls_fmp4_file(vhost);
hls_cleanup_ = config_->get_hls_cleanup(vhost);
hls_wait_keyframe_ = config_->get_hls_wait_keyframe(vhost);
// 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
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_fragments_per_key_ = _srs_config->get_hls_fragments_per_key(vhost);
hls_key_file_ = _srs_config->get_hls_key_file(vhost);
hls_key_file_path_ = _srs_config->get_hls_key_file_path(vhost);
hls_key_url_ = _srs_config->get_hls_key_url(vhost);
hls_keys_ = config_->get_hls_keys(vhost);
hls_fragments_per_key_ = config_->get_hls_fragments_per_key(vhost);
hls_key_file_ = config_->get_hls_key_file(vhost);
hls_key_file_path_ = config_->get_hls_key_file_path(vhost);
hls_key_url_ = config_->get_hls_key_url(vhost);
previous_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;
}
@ -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_));
string key_url = hls_key_file_path_ + "/" + key_file;
SrsFileWriter fw;
if ((err = fw.open(key_url)) != srs_success) {
SrsUniquePtr<ISrsFileWriter> fw(app_factory_->create_file_writer());
if ((err = fw->open(key_url)) != srs_success) {
return srs_error_wrap(err, "open file %s", key_url.c_str());
}
err = fw.write(key_, 16, NULL);
fw.close();
err = fw->write(key_, 16, NULL);
fw->close();
if (err != srs_success) {
return srs_error_wrap(err, "write key");
@ -937,23 +961,25 @@ srs_error_t SrsHlsFmp4Muxer::refresh_m3u8()
}
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) {
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.
if (srs_path_exists(temp_m3u8)) {
if (unlink(temp_m3u8.c_str()) < 0) {
srs_warn("ignore remove m3u8 failed, %s", temp_m3u8.c_str());
SrsUniquePtr<SrsPath> path(app_factory_->create_path());
if (path->exists(temp_m3u8)) {
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;
}
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;
@ -962,8 +988,8 @@ srs_error_t SrsHlsFmp4Muxer::_refresh_m3u8(std::string m3u8_file)
return err;
}
SrsFileWriter writer;
if ((err = writer.open(m3u8_file)) != srs_success) {
SrsUniquePtr<ISrsFileWriter> writer(app_factory_->create_file_writer());
if ((err = writer->open(m3u8_file)) != srs_success) {
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.
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");
}
@ -1080,6 +1106,9 @@ SrsHlsMuxer::SrsHlsMuxer()
memset(key_, 0, 16);
memset(iv_, 0, 16);
config_ = _srs_config;
app_factory_ = _srs_app_factory;
}
SrsHlsMuxer::~SrsHlsMuxer()
@ -1090,6 +1119,9 @@ SrsHlsMuxer::~SrsHlsMuxer()
srs_freep(async_);
srs_freep(context_);
srs_freep(writer_);
config_ = NULL;
app_factory_ = NULL;
}
void SrsHlsMuxer::dispose()
@ -1106,8 +1138,12 @@ void SrsHlsMuxer::dispose()
srs_freep(current_);
}
if (unlink(m3u8_.c_str()) < 0) {
srs_warn("dispose unlink path failed. file=%s", m3u8_.c_str());
SrsUniquePtr<SrsPath> path(app_factory_->create_path());
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() : "");
@ -1240,7 +1276,7 @@ srs_error_t SrsHlsMuxer::update_config(ISrsRequest *r, string entry_prefix,
m3u8_ = path + "/" + m3u8_url_;
// 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.
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_) {
writer_ = new SrsEncFileWriter();
writer_ = app_factory_->create_enc_file_writer();
} else {
writer_ = new SrsFileWriter();
writer_ = app_factory_->create_file_writer();
}
return err;
@ -1271,21 +1307,29 @@ srs_error_t SrsHlsMuxer::recover_hls()
srs_error_t err = srs_success;
// exist the m3u8 file.
if (!srs_path_exists(m3u8_)) {
SrsUniquePtr<SrsPath> path(app_factory_->create_path());
if (!path->exists(m3u8_)) {
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",
m3u8_.c_str(), m3u8_url_.c_str(), hls_path_.c_str());
// read whole m3u8 file content as a string
SrsFileReader fr;
if ((err = fr.open(m3u8_)) != srs_success) {
SrsUniquePtr<ISrsFileReader> fr(app_factory_->create_file_reader());
if ((err = fr->open(m3u8_)) != srs_success) {
return srs_error_wrap(err, "open file");
}
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");
}
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_));
string key_url = hls_key_file_path_ + "/" + key_file;
SrsFileWriter fw;
if ((err = fw.open(key_url)) != srs_success) {
SrsUniquePtr<ISrsFileWriter> fw(app_factory_->create_file_writer());
if ((err = fw->open(key_url)) != srs_success) {
return srs_error_wrap(err, "open file %s", key_url.c_str());
}
err = fw.write(key_, 16, NULL);
fw.close();
err = fw->write(key_, 16, NULL);
fw->close();
if (err != srs_success) {
return srs_error_wrap(err, "write key");
@ -1775,23 +1819,25 @@ srs_error_t SrsHlsMuxer::refresh_m3u8()
}
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) {
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.
if (srs_path_exists(temp_m3u8)) {
if (unlink(temp_m3u8.c_str()) < 0) {
srs_warn("ignore remove m3u8 failed, %s", temp_m3u8.c_str());
SrsUniquePtr<SrsPath> path(app_factory_->create_path());
if (path->exists(temp_m3u8)) {
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;
}
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;
@ -1800,8 +1846,8 @@ srs_error_t SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
return err;
}
SrsFileWriter writer;
if ((err = writer.open(m3u8_file)) != srs_success) {
SrsUniquePtr<ISrsFileWriter> writer(app_factory_->create_file_writer());
if ((err = writer->open(m3u8_file)) != srs_success) {
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.
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");
}
@ -1905,12 +1951,16 @@ SrsHlsController::SrsHlsController()
hls_dts_directly_ = false;
previous_audio_dts_ = 0;
aac_samples_ = 0;
config_ = _srs_config;
}
SrsHlsController::~SrsHlsController()
{
srs_freep(muxer_);
srs_freep(tsmc_);
config_ = NULL;
}
// 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 app = req->app_;
srs_utime_t hls_fragment = _srs_config->get_hls_fragment(vhost);
double hls_td_ratio = _srs_config->get_hls_td_ratio(vhost);
srs_utime_t hls_window = _srs_config->get_hls_window(vhost);
srs_utime_t hls_fragment = config_->get_hls_fragment(vhost);
double hls_td_ratio = config_->get_hls_td_ratio(vhost);
srs_utime_t hls_window = config_->get_hls_window(vhost);
// 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
std::string path = _srs_config->get_hls_path(vhost);
std::string m3u8_file = _srs_config->get_hls_m3u8_file(vhost);
std::string ts_file = _srs_config->get_hls_ts_file(vhost);
bool cleanup = _srs_config->get_hls_cleanup(vhost);
bool wait_keyframe = _srs_config->get_hls_wait_keyframe(vhost);
std::string path = config_->get_hls_path(vhost);
std::string m3u8_file = config_->get_hls_m3u8_file(vhost);
std::string ts_file = config_->get_hls_ts_file(vhost);
bool cleanup = config_->get_hls_cleanup(vhost);
bool wait_keyframe = config_->get_hls_wait_keyframe(vhost);
// 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
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.
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);
int hls_fragments_per_key = _srs_config->get_hls_fragments_per_key(vhost);
string hls_key_file = _srs_config->get_hls_key_file(vhost);
string hls_key_file_path = _srs_config->get_hls_key_file_path(vhost);
string hls_key_url = _srs_config->get_hls_key_url(vhost);
bool hls_keys = config_->get_hls_keys(vhost);
int hls_fragments_per_key = config_->get_hls_fragments_per_key(vhost);
string hls_key_file = config_->get_hls_key_file(vhost);
string hls_key_file_path = config_->get_hls_key_file_path(vhost);
string hls_key_url = config_->get_hls_key_url(vhost);
// TODO: FIXME: support load exists m3u8, to recover publish stream.
// 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) {
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.
// If enabled, directly turn FLV timestamp to TS DTS.
// @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",
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;
muxer_ = new SrsHlsFmp4Muxer();
config_ = _srs_config;
}
SrsHlsMp4Controller::~SrsHlsMp4Controller()
{
srs_freep(muxer_);
config_ = NULL;
}
// 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_;
// 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
std::string path = _srs_config->get_hls_path(vhost);
std::string m3u8_file = _srs_config->get_hls_m3u8_file(vhost);
std::string ts_file = _srs_config->get_hls_ts_file(vhost);
std::string path = config_->get_hls_path(vhost);
std::string m3u8_file = config_->get_hls_m3u8_file(vhost);
std::string ts_file = config_->get_hls_ts_file(vhost);
if ((err = muxer_->on_publish(req)) != srs_success) {
return srs_error_wrap(err, "muxer publish");
@ -2417,6 +2471,8 @@ SrsHls::SrsHls()
controller_ = NULL;
pprint_ = SrsPithyPrint::create_hls();
config_ = _srs_config;
}
SrsHls::~SrsHls()
@ -2424,6 +2480,8 @@ SrsHls::~SrsHls()
srs_freep(jitter_);
srs_freep(controller_);
srs_freep(pprint_);
config_ = NULL;
}
void SrsHls::async_reload()
@ -2483,7 +2541,7 @@ void SrsHls::dispose()
// Ignore when hls_dispose disabled.
// @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) {
return;
}
@ -2512,7 +2570,7 @@ srs_error_t SrsHls::cycle()
return err;
// 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) {
return err;
}
@ -2535,7 +2593,7 @@ srs_error_t SrsHls::cycle()
srs_utime_t SrsHls::cleanup_delay()
{
// 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
@ -2551,7 +2609,7 @@ srs_error_t SrsHls::initialize(SrsOriginHub *h, ISrsRequest *r)
hub_ = h;
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 (is_fmp4_enabled) {
@ -2580,7 +2638,7 @@ srs_error_t SrsHls::on_publish()
return err;
}
if (!_srs_config->get_hls_enabled(req_->vhost_)) {
if (!config_->get_hls_enabled(req_->vhost_)) {
return err;
}

View File

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

View File

@ -110,7 +110,8 @@
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")
/**************************************************/
/* RTMP protocol error. */

View File

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

View File

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

View File

@ -129,6 +129,20 @@ 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
{
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.
extern bool srs_path_exists(std::string path);
// Get the dirname of path, for instance, dirname("/live/livestream")="/live"

View File

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

View File

@ -51,6 +51,7 @@ bool _srs_config_by_env = false;
// @global kernel factory.
ISrsKernelFactory *_srs_kernel_factory = new SrsFinalFactory();
SrsAppFactory *_srs_app_factory = new SrsAppFactory();
// The binary name of SRS.
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
}
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)
{
http_hooks_enabled_ = enabled;

View File

@ -258,6 +258,37 @@ public:
virtual bool get_rtc_to_rtmp(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 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_on_stop_urls(const std::vector<std::string> &urls);
void clear_on_stop_directive();

View File

@ -12,8 +12,8 @@ using namespace std;
#include <srs_app_stream_token.hpp>
#include <srs_kernel_error.hpp>
#include <srs_kernel_rtc_rtp.hpp>
#include <srs_protocol_sdp.hpp>
#include <srs_protocol_rtc_stun.hpp>
#include <srs_protocol_sdp.hpp>
#include <srs_utest_app5.hpp>
#include <srs_utest_app6.hpp>
@ -1172,10 +1172,6 @@ ISrsRequest *MockRtcConnectionRequest::as_http()
return copy();
}
VOID TEST(SrsRtcConnectionTest, OnDtlsHandshakeDoneTypicalScenario)
{
srs_error_t err;
@ -1347,8 +1343,8 @@ VOID TEST(SrsRtcConnectionTest, CreatePublisherTypicalScenario)
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
audio_desc->type_ = "audio";
audio_desc->ssrc_ = 0x12345678;
audio_desc->fec_ssrc_ = 0x12345679; // Different FEC SSRC
audio_desc->rtx_ssrc_ = 0x1234567A; // Different RTX SSRC
audio_desc->fec_ssrc_ = 0x12345679; // Different FEC SSRC
audio_desc->rtx_ssrc_ = 0x1234567A; // Different RTX SSRC
audio_desc->id_ = "test-audio-track";
audio_desc->is_active_ = true;
audio_desc->direction_ = "sendrecv";
@ -1358,8 +1354,8 @@ VOID TEST(SrsRtcConnectionTest, CreatePublisherTypicalScenario)
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
video_desc->type_ = "video";
video_desc->ssrc_ = 0x87654321;
video_desc->fec_ssrc_ = 0x87654322; // Different FEC SSRC
video_desc->rtx_ssrc_ = 0x87654323; // Different RTX SSRC
video_desc->fec_ssrc_ = 0x87654322; // Different FEC SSRC
video_desc->rtx_ssrc_ = 0x87654323; // Different RTX SSRC
video_desc->id_ = "test-video-track";
video_desc->is_active_ = true;
video_desc->direction_ = "sendrecv";
@ -1380,7 +1376,7 @@ VOID TEST(SrsRtcConnectionTest, CreatePublisherTypicalScenario)
// Test the early return logic for existing publisher
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
// Test scenario 3: Test duplicate SSRC error detection
@ -1446,7 +1442,7 @@ VOID TEST(SrsRtcConnectionTest, CreatePublisherTypicalScenario)
}
// 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_[0x12345679]); // Audio FEC
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
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
// 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
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(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(201, (unsigned char)rtcp_data[1]); // PT=201 (RR)
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)
}
// Test error handling scenario
@ -1790,7 +1786,7 @@ VOID TEST(SrsRtcConnectionTest, AddPublisherTypicalScenario)
SrsUniquePtr<MockRtcSourceManager> mock_rtc_sources(new MockRtcSourceManager());
// 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);
// 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)
ruc->remote_sdp_str_ = "v=0\r\n"
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
"s=-\r\n"
"t=0 0\r\n"
"m=video 9 UDP/TLS/RTP/SAVPF 96\r\n"
"a=rtpmap:96 H264/90000\r\n"
"a=fmtp:96 profile-level-id=42e01f\r\n"
"a=sendonly\r\n";
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
"s=-\r\n"
"t=0 0\r\n"
"m=video 9 UDP/TLS/RTP/SAVPF 96\r\n"
"a=rtpmap:96 H264/90000\r\n"
"a=fmtp:96 profile-level-id=42e01f\r\n"
"a=sendonly\r\n";
// Parse the remote SDP
HELPER_EXPECT_SUCCESS(ruc->remote_sdp_.parse(ruc->remote_sdp_str_));
@ -1868,8 +1864,7 @@ VOID TEST(SrsRtcConnectionTest, OnRtpCipherTypicalScenario)
0x56, 0x78, 0x9A, 0xBC, // timestamp
0x12, 0x34, 0x56, 0x78, // SSRC = 0x12345678
// RTP payload (sample data)
0x01, 0x02, 0x03, 0x04
};
0x01, 0x02, 0x03, 0x04};
// Add the publish stream to the connection's publishers map
string stream_url = "/live/test";
@ -1980,8 +1975,10 @@ VOID TEST(SrsRtcPublisherNegotiatorTest, TypicalUseScenario)
// Find audio and video media descriptions
bool has_audio = false, has_video = false;
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_ == "video") has_video = true;
if (local_sdp.media_descs_[i].type_ == "audio")
has_audio = true;
if (local_sdp.media_descs_[i].type_ == "video")
has_video = true;
}
EXPECT_TRUE(has_audio);
EXPECT_TRUE(has_video);
@ -2223,8 +2220,7 @@ VOID TEST(SrsRtcConnectionTest, OnRtpPlaintextTypicalScenario)
0x56, 0x78, 0x9A, 0xBC, // timestamp
0x12, 0x34, 0x56, 0x78, // SSRC = 0x12345678
// 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
SrsUniquePtr<SrsRtcTrackDescription> video_desc(create_video_track_description_with_codec("H264", test_ssrc));
@ -2521,8 +2517,6 @@ VOID TEST(SrsRtcConnectionTest, DoCheckSendNacksTypicalScenario)
conn->publishers_.clear();
}
VOID TEST(SdpUtilityTest, SrsSDPHasH264ProfilePayloadTypeTypicalScenario)
{
// Test srs_sdp_has_h264_profile with SrsMediaPayloadType - typical scenario
@ -2661,7 +2655,7 @@ VOID TEST(SrsRtcPlayerNegotiatorTest, TypicalUseScenario)
SrsUniquePtr<SrsRtcSourceDescription> stream_desc(new SrsRtcSourceDescription());
// Create audio track description (managed by stream_desc)
SrsRtcTrackDescription* audio_track = new SrsRtcTrackDescription();
SrsRtcTrackDescription *audio_track = new SrsRtcTrackDescription();
audio_track->type_ = "audio";
audio_track->id_ = "audio_track_id";
audio_track->ssrc_ = 12345;
@ -2673,7 +2667,7 @@ VOID TEST(SrsRtcPlayerNegotiatorTest, TypicalUseScenario)
audio_track->media_ = new SrsAudioPayload(111, "opus", 48000, 2);
// Create video track description (managed by stream_desc)
SrsRtcTrackDescription* video_track = new SrsRtcTrackDescription();
SrsRtcTrackDescription *video_track = new SrsRtcTrackDescription();
video_track->type_ = "video";
video_track->id_ = "video_track_id";
video_track->ssrc_ = 67890;
@ -2694,9 +2688,9 @@ VOID TEST(SrsRtcPlayerNegotiatorTest, TypicalUseScenario)
mock_request.get(),
local_sdp,
stream_desc.get(),
true, // unified_plan
true // audio_before_video
));
true, // unified_plan
true // audio_before_video
));
// Verify the generated local SDP has the expected structure
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_source.hpp>
#include <srs_kernel_rtc_queue.hpp>
#include <srs_utest_app6.hpp>
#include <srs_utest_app2.hpp>
#include <srs_utest_service.hpp>
#include <srs_utest_app6.hpp>
#include <srs_utest_kernel3.hpp>
#include <srs_utest_service.hpp>
// Mock video recv track for testing check_send_nacks
class MockRtcVideoRecvTrackForNack : public SrsRtcVideoRecvTrack
@ -157,6 +157,4 @@ public:
virtual ISrsRequest *as_http();
};
#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