AI: Improve utest coverage for HLS.
This commit is contained in:
parent
758906353c
commit
1faadd0c73
|
|
@ -709,6 +709,20 @@ srs_error_t SrsHlsFmp4Muxer::update_config(ISrsRequest *r)
|
|||
max_td_ = hls_fragment_ * hls_td_ratio;
|
||||
|
||||
// create m3u8 dir once.
|
||||
if ((err = create_directories()) != srs_success) {
|
||||
return srs_error_wrap(err, "create dir");
|
||||
}
|
||||
|
||||
writer_ = app_factory_->create_file_writer();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
srs_error_t SrsHlsFmp4Muxer::create_directories()
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
SrsPath path;
|
||||
m3u8_dir_ = path.filepath_dir(m3u8_);
|
||||
if ((err = path.mkdir_all(m3u8_dir_)) != srs_success) {
|
||||
|
|
@ -716,7 +730,7 @@ srs_error_t SrsHlsFmp4Muxer::update_config(ISrsRequest *r)
|
|||
}
|
||||
|
||||
if (hls_keys_ && (hls_path_ != hls_key_file_path_)) {
|
||||
string key_file = srs_path_build_stream(hls_key_file_, vhost, app, stream);
|
||||
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
||||
string key_url = hls_key_file_path_ + "/" + key_file;
|
||||
string key_dir = path.filepath_dir(key_url);
|
||||
if ((err = path.mkdir_all(key_dir)) != srs_success) {
|
||||
|
|
@ -724,10 +738,9 @@ srs_error_t SrsHlsFmp4Muxer::update_config(ISrsRequest *r)
|
|||
}
|
||||
}
|
||||
|
||||
writer_ = app_factory_->create_file_writer();
|
||||
|
||||
return err;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
srs_error_t SrsHlsFmp4Muxer::segment_open(srs_utime_t basetime)
|
||||
{
|
||||
|
|
@ -747,8 +760,46 @@ srs_error_t SrsHlsFmp4Muxer::segment_open(srs_utime_t basetime)
|
|||
}
|
||||
|
||||
// generate filename.
|
||||
std::string m4s_file = generate_m4s_filename();
|
||||
|
||||
std::string m4s_path = hls_path_ + "/" + m4s_file;
|
||||
current_->set_path(m4s_path);
|
||||
|
||||
// the ts url, relative or absolute url.
|
||||
// TODO: FIXME: Use url and path manager.
|
||||
std::string m4s_url = current_->fullpath();
|
||||
if (srs_strings_starts_with(m4s_url, m3u8_dir_)) {
|
||||
m4s_url = m4s_url.substr(m3u8_dir_.length());
|
||||
}
|
||||
while (srs_strings_starts_with(m4s_url, "/")) {
|
||||
m4s_url = m4s_url.substr(1);
|
||||
}
|
||||
|
||||
current_->uri_ += hls_entry_prefix_;
|
||||
if (!hls_entry_prefix_.empty() && !srs_strings_ends_with(hls_entry_prefix_, "/")) {
|
||||
current_->uri_ += "/";
|
||||
|
||||
// add the http dir to uri.
|
||||
SrsPath path;
|
||||
string http_dir = path.filepath_dir(m3u8_url_);
|
||||
if (!http_dir.empty()) {
|
||||
current_->uri_ += http_dir + "/";
|
||||
}
|
||||
}
|
||||
current_->uri_ += m4s_url;
|
||||
|
||||
current_->initialize(basetime, video_track_id_, audio_track_id_, sequence_no_, m4s_path);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
std::string SrsHlsFmp4Muxer::generate_m4s_filename()
|
||||
{
|
||||
|
||||
std::string m4s_file = hls_m4s_file_;
|
||||
|
||||
m4s_file = srs_path_build_stream(m4s_file, req_->vhost_, req_->app_, req_->stream_);
|
||||
|
||||
if (hls_ts_floor_) {
|
||||
// accept the floor ts for the first piece.
|
||||
int64_t current_floor_ts = srs_time_now_realtime() / hls_fragment_;
|
||||
|
|
@ -784,41 +835,14 @@ srs_error_t SrsHlsFmp4Muxer::segment_open(srs_utime_t basetime)
|
|||
} else {
|
||||
m4s_file = srs_path_build_timestamp(m4s_file);
|
||||
}
|
||||
|
||||
if (true) {
|
||||
std::stringstream ss;
|
||||
ss << current_->sequence_no_;
|
||||
m4s_file = srs_strings_replace(m4s_file, "[seq]", ss.str());
|
||||
}
|
||||
|
||||
std::string m4s_path = hls_path_ + "/" + m4s_file;
|
||||
current_->set_path(m4s_path);
|
||||
|
||||
// the ts url, relative or absolute url.
|
||||
// TODO: FIXME: Use url and path manager.
|
||||
std::string m4s_url = current_->fullpath();
|
||||
if (srs_strings_starts_with(m4s_url, m3u8_dir_)) {
|
||||
m4s_url = m4s_url.substr(m3u8_dir_.length());
|
||||
}
|
||||
while (srs_strings_starts_with(m4s_url, "/")) {
|
||||
m4s_url = m4s_url.substr(1);
|
||||
}
|
||||
|
||||
current_->uri_ += hls_entry_prefix_;
|
||||
if (!hls_entry_prefix_.empty() && !srs_strings_ends_with(hls_entry_prefix_, "/")) {
|
||||
current_->uri_ += "/";
|
||||
|
||||
// add the http dir to uri.
|
||||
SrsPath path;
|
||||
string http_dir = path.filepath_dir(m3u8_url_);
|
||||
if (!http_dir.empty()) {
|
||||
current_->uri_ += http_dir + "/";
|
||||
}
|
||||
}
|
||||
current_->uri_ += m4s_url;
|
||||
|
||||
current_->initialize(basetime, video_track_id_, audio_track_id_, sequence_no_, m4s_path);
|
||||
|
||||
return err;
|
||||
return m4s_file;
|
||||
}
|
||||
|
||||
srs_error_t SrsHlsFmp4Muxer::on_sequence_header()
|
||||
|
|
@ -1033,47 +1057,9 @@ srs_error_t SrsHlsFmp4Muxer::do_refresh_m3u8(std::string m3u8_file)
|
|||
// write all segments
|
||||
for (int i = 0; i < segments_->size(); i++) {
|
||||
SrsHlsM4sSegment *segment = dynamic_cast<SrsHlsM4sSegment *>(segments_->at(i));
|
||||
|
||||
if (segment->is_sequence_header()) {
|
||||
// #EXT-X-DISCONTINUITY\n
|
||||
ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF;
|
||||
if ((err = do_refresh_m3u8_segment(segment, ss)) != srs_success) {
|
||||
return srs_error_wrap(err, "hls: refresh m3u8 segment");
|
||||
}
|
||||
|
||||
#if 1
|
||||
if (hls_keys_ && ((segment->sequence_no_ % hls_fragments_per_key_) == 0)) {
|
||||
char hexiv[33];
|
||||
srs_hex_encode_to_string(hexiv, segment->iv_, 16);
|
||||
hexiv[32] = '\0';
|
||||
|
||||
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
||||
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(segment->sequence_no_));
|
||||
|
||||
string key_path = key_file;
|
||||
// if key_url is not set,only use the file name
|
||||
if (!hls_key_url_.empty()) {
|
||||
key_path = hls_key_url_ + key_file;
|
||||
}
|
||||
|
||||
ss << "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF;
|
||||
}
|
||||
#endif
|
||||
|
||||
// "#EXTINF:4294967295.208,\n"
|
||||
ss.precision(3);
|
||||
ss.setf(std::ios::fixed, std::ios::floatfield);
|
||||
ss << "#EXTINF:" << srsu2msi(segment->duration()) / 1000.0 << ", no desc" << SRS_CONSTS_LF;
|
||||
|
||||
// {file name}\n
|
||||
// TODO get segment name in relative path.
|
||||
SrsPath path;
|
||||
std::string seg_uri = segment->fullpath();
|
||||
if (true) {
|
||||
std::stringstream stemp;
|
||||
stemp << srsu2msi(segment->duration());
|
||||
seg_uri = srs_strings_replace(seg_uri, "[duration]", stemp.str());
|
||||
}
|
||||
// ss << segment->uri << SRS_CONSTS_LF;
|
||||
ss << path.filepath_base(seg_uri) << SRS_CONSTS_LF;
|
||||
}
|
||||
|
||||
// write m3u8 to writer.
|
||||
|
|
@ -1085,6 +1071,60 @@ srs_error_t SrsHlsFmp4Muxer::do_refresh_m3u8(std::string m3u8_file)
|
|||
return err;
|
||||
}
|
||||
|
||||
srs_error_t SrsHlsFmp4Muxer::do_refresh_m3u8_segment(SrsHlsM4sSegment *segment, std::stringstream &ss)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if (segment->is_sequence_header()) {
|
||||
// #EXT-X-DISCONTINUITY\n
|
||||
ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF;
|
||||
}
|
||||
|
||||
if (hls_keys_ && ((segment->sequence_no_ % hls_fragments_per_key_) == 0)) {
|
||||
char hexiv[33];
|
||||
srs_hex_encode_to_string(hexiv, segment->iv_, 16);
|
||||
hexiv[32] = '\0';
|
||||
|
||||
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
||||
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(segment->sequence_no_));
|
||||
|
||||
string key_path = key_file;
|
||||
// if key_url is not set,only use the file name
|
||||
if (!hls_key_url_.empty()) {
|
||||
key_path = hls_key_url_ + key_file;
|
||||
}
|
||||
|
||||
ss << "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF;
|
||||
}
|
||||
|
||||
// "#EXTINF:4294967295.208,\n"
|
||||
ss.precision(3);
|
||||
ss.setf(std::ios::fixed, std::ios::floatfield);
|
||||
ss << "#EXTINF:" << srsu2msi(segment->duration()) / 1000.0 << ", no desc" << SRS_CONSTS_LF;
|
||||
|
||||
// {file name}\n
|
||||
// TODO get segment name in relative path.
|
||||
SrsPath path;
|
||||
std::string seg_uri = segment->fullpath();
|
||||
if (true) {
|
||||
std::stringstream stemp;
|
||||
stemp << srsu2msi(segment->duration());
|
||||
seg_uri = srs_strings_replace(seg_uri, "[duration]", stemp.str());
|
||||
}
|
||||
// ss << segment->uri << SRS_CONSTS_LF;
|
||||
ss << path.filepath_base(seg_uri) << SRS_CONSTS_LF;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
ISrsHlsMuxer::ISrsHlsMuxer()
|
||||
{
|
||||
}
|
||||
|
||||
ISrsHlsMuxer::~ISrsHlsMuxer()
|
||||
{
|
||||
}
|
||||
|
||||
SrsHlsMuxer::SrsHlsMuxer()
|
||||
{
|
||||
req_ = NULL;
|
||||
|
|
@ -1128,6 +1168,7 @@ SrsHlsMuxer::~SrsHlsMuxer()
|
|||
app_factory_ = NULL;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
void SrsHlsMuxer::dispose()
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
|
@ -1152,6 +1193,7 @@ void SrsHlsMuxer::dispose()
|
|||
|
||||
srs_trace("gracefully dispose hls %s", req_ ? req_->get_stream_url().c_str() : "");
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
int SrsHlsMuxer::sequence_no()
|
||||
{
|
||||
|
|
@ -1282,6 +1324,24 @@ srs_error_t SrsHlsMuxer::update_config(ISrsRequest *r, string entry_prefix,
|
|||
// when update config, reset the history target duration.
|
||||
max_td_ = fragment * config_->get_hls_td_ratio(r->vhost_);
|
||||
|
||||
if ((err = create_directories()) != srs_success) {
|
||||
return srs_error_wrap(err, "create dir");
|
||||
}
|
||||
|
||||
if (hls_keys_) {
|
||||
writer_ = app_factory_->create_enc_file_writer();
|
||||
} else {
|
||||
writer_ = app_factory_->create_file_writer();
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
srs_error_t SrsHlsMuxer::create_directories()
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// create m3u8 dir once.
|
||||
SrsPath path_util;
|
||||
m3u8_dir_ = path_util.filepath_dir(m3u8_);
|
||||
|
|
@ -1298,14 +1358,9 @@ srs_error_t SrsHlsMuxer::update_config(ISrsRequest *r, string entry_prefix,
|
|||
}
|
||||
}
|
||||
|
||||
if (hls_keys_) {
|
||||
writer_ = app_factory_->create_enc_file_writer();
|
||||
} else {
|
||||
writer_ = app_factory_->create_file_writer();
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
srs_error_t SrsHlsMuxer::recover_hls()
|
||||
{
|
||||
|
|
@ -1496,48 +1551,7 @@ srs_error_t SrsHlsMuxer::segment_open()
|
|||
}
|
||||
|
||||
// generate filename.
|
||||
std::string ts_file = hls_ts_file_;
|
||||
ts_file = srs_path_build_stream(ts_file, req_->vhost_, req_->app_, req_->stream_);
|
||||
if (hls_ts_floor_) {
|
||||
// accept the floor ts for the first piece.
|
||||
int64_t current_floor_ts = srs_time_now_realtime() / hls_fragment_;
|
||||
if (!accept_floor_ts_) {
|
||||
accept_floor_ts_ = current_floor_ts - 1;
|
||||
} else {
|
||||
accept_floor_ts_++;
|
||||
}
|
||||
|
||||
// jump when deviation more than 10p
|
||||
if (accept_floor_ts_ - current_floor_ts > SRS_JUMP_WHEN_PIECE_DEVIATION) {
|
||||
srs_warn("hls: jmp for ts deviation, current=%" PRId64 ", accept=%" PRId64, current_floor_ts, accept_floor_ts_);
|
||||
accept_floor_ts_ = current_floor_ts - 1;
|
||||
}
|
||||
|
||||
// when reap ts, adjust the deviation.
|
||||
deviation_ts_ = (int)(accept_floor_ts_ - current_floor_ts);
|
||||
|
||||
// dup/jmp detect for ts in floor mode.
|
||||
if (previous_floor_ts_ && previous_floor_ts_ != current_floor_ts - 1) {
|
||||
srs_warn("hls: dup/jmp ts, previous=%" PRId64 ", current=%" PRId64 ", accept=%" PRId64 ", deviation=%d",
|
||||
previous_floor_ts_, current_floor_ts, accept_floor_ts_, deviation_ts_);
|
||||
}
|
||||
previous_floor_ts_ = current_floor_ts;
|
||||
|
||||
// we always ensure the piece is increase one by one.
|
||||
std::stringstream ts_floor;
|
||||
ts_floor << accept_floor_ts_;
|
||||
ts_file = srs_strings_replace(ts_file, "[timestamp]", ts_floor.str());
|
||||
|
||||
// TODO: FIMXE: we must use the accept ts floor time to generate the hour variable.
|
||||
ts_file = srs_path_build_timestamp(ts_file);
|
||||
} else {
|
||||
ts_file = srs_path_build_timestamp(ts_file);
|
||||
}
|
||||
if (true) {
|
||||
std::stringstream ss;
|
||||
ss << current_->sequence_no_;
|
||||
ts_file = srs_strings_replace(ts_file, "[seq]", ss.str());
|
||||
}
|
||||
std::string ts_file = generate_ts_filename();
|
||||
current_->set_path(hls_path_ + "/" + ts_file);
|
||||
|
||||
// the ts url, relative or absolute url.
|
||||
|
|
@ -1579,6 +1593,57 @@ srs_error_t SrsHlsMuxer::segment_open()
|
|||
return err;
|
||||
}
|
||||
|
||||
string SrsHlsMuxer::generate_ts_filename()
|
||||
{
|
||||
std::string ts_file = hls_ts_file_;
|
||||
|
||||
ts_file = srs_path_build_stream(ts_file, req_->vhost_, req_->app_, req_->stream_);
|
||||
|
||||
if (hls_ts_floor_) {
|
||||
// accept the floor ts for the first piece.
|
||||
int64_t current_floor_ts = srs_time_now_realtime() / hls_fragment_;
|
||||
if (!accept_floor_ts_) {
|
||||
accept_floor_ts_ = current_floor_ts - 1;
|
||||
} else {
|
||||
accept_floor_ts_++;
|
||||
}
|
||||
|
||||
// jump when deviation more than 10p
|
||||
if (accept_floor_ts_ - current_floor_ts > SRS_JUMP_WHEN_PIECE_DEVIATION) {
|
||||
srs_warn("hls: jmp for ts deviation, current=%" PRId64 ", accept=%" PRId64, current_floor_ts, accept_floor_ts_);
|
||||
accept_floor_ts_ = current_floor_ts - 1;
|
||||
}
|
||||
|
||||
// when reap ts, adjust the deviation.
|
||||
deviation_ts_ = (int)(accept_floor_ts_ - current_floor_ts);
|
||||
|
||||
// dup/jmp detect for ts in floor mode.
|
||||
if (previous_floor_ts_ && previous_floor_ts_ != current_floor_ts - 1) {
|
||||
srs_warn("hls: dup/jmp ts, previous=%" PRId64 ", current=%" PRId64 ", accept=%" PRId64 ", deviation=%d",
|
||||
previous_floor_ts_, current_floor_ts, accept_floor_ts_, deviation_ts_);
|
||||
}
|
||||
previous_floor_ts_ = current_floor_ts;
|
||||
|
||||
// we always ensure the piece is increase one by one.
|
||||
std::stringstream ts_floor;
|
||||
ts_floor << accept_floor_ts_;
|
||||
ts_file = srs_strings_replace(ts_file, "[timestamp]", ts_floor.str());
|
||||
|
||||
// TODO: FIMXE: we must use the accept ts floor time to generate the hour variable.
|
||||
ts_file = srs_path_build_timestamp(ts_file);
|
||||
} else {
|
||||
ts_file = srs_path_build_timestamp(ts_file);
|
||||
}
|
||||
|
||||
if (true) {
|
||||
std::stringstream ss;
|
||||
ss << current_->sequence_no_;
|
||||
ts_file = srs_strings_replace(ts_file, "[seq]", ss.str());
|
||||
}
|
||||
|
||||
return ts_file;
|
||||
}
|
||||
|
||||
srs_error_t SrsHlsMuxer::on_sequence_header()
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
|
@ -1713,6 +1778,31 @@ srs_error_t SrsHlsMuxer::do_segment_close()
|
|||
return err;
|
||||
}
|
||||
|
||||
if ((err = do_segment_close2()) != srs_success) {
|
||||
return srs_error_wrap(err, "hls: do segment close");
|
||||
}
|
||||
|
||||
// shrink the segments.
|
||||
segments_->shrink(hls_window_);
|
||||
|
||||
// refresh the m3u8, donot contains the removed ts
|
||||
err = refresh_m3u8();
|
||||
|
||||
// remove the ts file.
|
||||
segments_->clear_expired(hls_cleanup_);
|
||||
|
||||
// check ret of refresh m3u8
|
||||
if (err != srs_success) {
|
||||
return srs_error_wrap(err, "hls: refresh m3u8");
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
srs_error_t SrsHlsMuxer::do_segment_close2()
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// when close current segment, the current segment must not be NULL.
|
||||
srs_assert(current_);
|
||||
|
||||
|
|
@ -1762,20 +1852,6 @@ srs_error_t SrsHlsMuxer::do_segment_close()
|
|||
}
|
||||
}
|
||||
|
||||
// shrink the segments.
|
||||
segments_->shrink(hls_window_);
|
||||
|
||||
// refresh the m3u8, donot contains the removed ts
|
||||
err = refresh_m3u8();
|
||||
|
||||
// remove the ts file.
|
||||
segments_->clear_expired(hls_cleanup_);
|
||||
|
||||
// check ret of refresh m3u8
|
||||
if (err != srs_success) {
|
||||
return srs_error_wrap(err, "hls: refresh m3u8");
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -1815,6 +1891,7 @@ srs_error_t SrsHlsMuxer::write_hls_key()
|
|||
return err;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
srs_error_t SrsHlsMuxer::refresh_m3u8()
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
|
@ -1842,6 +1919,7 @@ srs_error_t SrsHlsMuxer::refresh_m3u8()
|
|||
|
||||
return err;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
srs_error_t SrsHlsMuxer::do_refresh_m3u8(string m3u8_file)
|
||||
{
|
||||
|
|
@ -1893,43 +1971,9 @@ srs_error_t SrsHlsMuxer::do_refresh_m3u8(string m3u8_file)
|
|||
// write all segments
|
||||
for (int i = 0; i < segments_->size(); i++) {
|
||||
SrsHlsSegment *segment = dynamic_cast<SrsHlsSegment *>(segments_->at(i));
|
||||
|
||||
if (segment->is_sequence_header()) {
|
||||
// #EXT-X-DISCONTINUITY\n
|
||||
ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF;
|
||||
if ((err = do_refresh_m3u8_segment(segment, ss)) != srs_success) {
|
||||
return srs_error_wrap(err, "hls: refresh m3u8 segment");
|
||||
}
|
||||
|
||||
if (hls_keys_ && ((segment->sequence_no_ % hls_fragments_per_key_) == 0)) {
|
||||
char hexiv[33];
|
||||
srs_hex_encode_to_string(hexiv, segment->iv_, 16);
|
||||
hexiv[32] = '\0';
|
||||
|
||||
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
||||
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(segment->sequence_no_));
|
||||
|
||||
string key_path = key_file;
|
||||
// if key_url is not set,only use the file name
|
||||
if (!hls_key_url_.empty()) {
|
||||
key_path = hls_key_url_ + key_file;
|
||||
}
|
||||
|
||||
ss << "#EXT-X-KEY:METHOD=AES-128,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF;
|
||||
}
|
||||
|
||||
// "#EXTINF:4294967295.208,\n"
|
||||
ss.precision(3);
|
||||
ss.setf(std::ios::fixed, std::ios::floatfield);
|
||||
ss << "#EXTINF:" << srsu2msi(segment->duration()) / 1000.0 << ", no desc" << SRS_CONSTS_LF;
|
||||
|
||||
// {file name}\n
|
||||
std::string seg_uri = segment->uri_;
|
||||
if (true) {
|
||||
std::stringstream stemp;
|
||||
stemp << srsu2msi(segment->duration());
|
||||
seg_uri = srs_strings_replace(seg_uri, "[duration]", stemp.str());
|
||||
}
|
||||
// ss << segment->uri << SRS_CONSTS_LF;
|
||||
ss << seg_uri << SRS_CONSTS_LF;
|
||||
}
|
||||
|
||||
// write m3u8 to writer.
|
||||
|
|
@ -1941,6 +1985,50 @@ srs_error_t SrsHlsMuxer::do_refresh_m3u8(string m3u8_file)
|
|||
return err;
|
||||
}
|
||||
|
||||
srs_error_t SrsHlsMuxer::do_refresh_m3u8_segment(SrsHlsSegment *segment, std::stringstream &ss)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if (segment->is_sequence_header()) {
|
||||
// #EXT-X-DISCONTINUITY\n
|
||||
ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF;
|
||||
}
|
||||
|
||||
if (hls_keys_ && ((segment->sequence_no_ % hls_fragments_per_key_) == 0)) {
|
||||
char hexiv[33];
|
||||
srs_hex_encode_to_string(hexiv, segment->iv_, 16);
|
||||
hexiv[32] = '\0';
|
||||
|
||||
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
||||
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(segment->sequence_no_));
|
||||
|
||||
string key_path = key_file;
|
||||
// if key_url is not set,only use the file name
|
||||
if (!hls_key_url_.empty()) {
|
||||
key_path = hls_key_url_ + key_file;
|
||||
}
|
||||
|
||||
ss << "#EXT-X-KEY:METHOD=AES-128,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF;
|
||||
}
|
||||
|
||||
// "#EXTINF:4294967295.208,\n"
|
||||
ss.precision(3);
|
||||
ss.setf(std::ios::fixed, std::ios::floatfield);
|
||||
ss << "#EXTINF:" << srsu2msi(segment->duration()) / 1000.0 << ", no desc" << SRS_CONSTS_LF;
|
||||
|
||||
// {file name}\n
|
||||
std::string seg_uri = segment->uri_;
|
||||
if (true) {
|
||||
std::stringstream stemp;
|
||||
stemp << srsu2msi(segment->duration());
|
||||
seg_uri = srs_strings_replace(seg_uri, "[duration]", stemp.str());
|
||||
}
|
||||
// ss << segment->uri << SRS_CONSTS_LF;
|
||||
ss << seg_uri << SRS_CONSTS_LF;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
ISrsHlsController::ISrsHlsController()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include <srs_app_async_call.hpp>
|
||||
#include <srs_app_fragment.hpp>
|
||||
|
|
@ -184,6 +185,52 @@ public:
|
|||
virtual std::string to_string();
|
||||
};
|
||||
|
||||
// The HLS muxer interface.
|
||||
class ISrsHlsMuxer
|
||||
{
|
||||
public:
|
||||
ISrsHlsMuxer();
|
||||
virtual ~ISrsHlsMuxer();
|
||||
|
||||
public:
|
||||
virtual srs_error_t initialize() = 0;
|
||||
virtual void dispose() = 0;
|
||||
|
||||
public:
|
||||
virtual int sequence_no() = 0;
|
||||
virtual std::string ts_url() = 0;
|
||||
virtual srs_utime_t duration() = 0;
|
||||
virtual int deviation() = 0;
|
||||
|
||||
public:
|
||||
virtual SrsAudioCodecId latest_acodec() = 0;
|
||||
virtual void set_latest_acodec(SrsAudioCodecId v) = 0;
|
||||
virtual SrsVideoCodecId latest_vcodec() = 0;
|
||||
virtual void set_latest_vcodec(SrsVideoCodecId v) = 0;
|
||||
|
||||
public:
|
||||
virtual bool pure_audio() = 0;
|
||||
virtual bool is_segment_overflow() = 0;
|
||||
virtual bool is_segment_absolutely_overflow() = 0;
|
||||
virtual bool wait_keyframe() = 0;
|
||||
|
||||
public:
|
||||
virtual srs_error_t on_publish(ISrsRequest *req) = 0;
|
||||
virtual srs_error_t on_unpublish() = 0;
|
||||
virtual srs_error_t update_config(ISrsRequest *r, std::string entry_prefix,
|
||||
std::string path, std::string m3u8_file, std::string ts_file,
|
||||
srs_utime_t fragment, srs_utime_t window, bool ts_floor, double aof_ratio,
|
||||
bool cleanup, bool wait_keyframe, bool keys, int fragments_per_key,
|
||||
std::string key_file, std::string key_file_path, std::string key_url) = 0;
|
||||
virtual srs_error_t segment_open() = 0;
|
||||
virtual srs_error_t on_sequence_header() = 0;
|
||||
virtual srs_error_t flush_audio(SrsTsMessageCache *cache) = 0;
|
||||
virtual srs_error_t flush_video(SrsTsMessageCache *cache) = 0;
|
||||
virtual void update_duration(uint64_t dts) = 0;
|
||||
virtual srs_error_t segment_close() = 0;
|
||||
virtual srs_error_t recover_hls() = 0;
|
||||
};
|
||||
|
||||
// Mux the HLS stream(m3u8 and ts files).
|
||||
// Generally, the m3u8 muxer only provides methods to open/close segments,
|
||||
// to flush video/audio, without any mechenisms.
|
||||
|
|
@ -191,7 +238,7 @@ public:
|
|||
// That is, user must use HlsCache, which will control the methods of muxer,
|
||||
// and provides HLS mechenisms.
|
||||
// TODO: Rename to SrsHlsTsMuxer, for TS file only.
|
||||
class SrsHlsMuxer
|
||||
class SrsHlsMuxer : public ISrsHlsMuxer
|
||||
{
|
||||
// clang-format off
|
||||
SRS_DECLARE_PRIVATE: // clang-format on
|
||||
|
|
@ -299,8 +346,20 @@ public:
|
|||
srs_utime_t fragment, srs_utime_t window, bool ts_floor, double aof_ratio,
|
||||
bool cleanup, bool wait_keyframe, bool keys, int fragments_per_key,
|
||||
std::string key_file, std::string key_file_path, std::string key_url);
|
||||
|
||||
// clang-format off
|
||||
SRS_DECLARE_PRIVATE: // clang-format on
|
||||
virtual srs_error_t create_directories();
|
||||
|
||||
public:
|
||||
// Open a new segment(a new ts file)
|
||||
virtual srs_error_t segment_open();
|
||||
|
||||
// clang-format off
|
||||
SRS_DECLARE_PRIVATE: // clang-format on
|
||||
virtual std::string generate_ts_filename();
|
||||
|
||||
public:
|
||||
virtual srs_error_t on_sequence_header();
|
||||
// Whether segment overflow,
|
||||
// that is whether the current segment duration>=(the segment in config)
|
||||
|
|
@ -326,9 +385,11 @@ public:
|
|||
// clang-format off
|
||||
SRS_DECLARE_PRIVATE: // clang-format on
|
||||
virtual srs_error_t do_segment_close();
|
||||
virtual srs_error_t do_segment_close2();
|
||||
virtual srs_error_t write_hls_key();
|
||||
virtual srs_error_t refresh_m3u8();
|
||||
virtual srs_error_t do_refresh_m3u8(std::string m3u8_file);
|
||||
virtual srs_error_t do_refresh_m3u8_segment(SrsHlsSegment *segment, std::stringstream &ss);
|
||||
// Check if a segment with the given URI already exists in the segments list.
|
||||
virtual bool segment_exists(const std::string &ts_url);
|
||||
|
||||
|
|
@ -468,8 +529,20 @@ public:
|
|||
virtual srs_error_t on_unpublish();
|
||||
// When publish, update the config for muxer.
|
||||
virtual srs_error_t update_config(ISrsRequest *r);
|
||||
|
||||
// clang-format off
|
||||
SRS_DECLARE_PRIVATE: // clang-format on
|
||||
virtual srs_error_t create_directories();
|
||||
|
||||
public:
|
||||
// Open a new segment(a new ts file)
|
||||
virtual srs_error_t segment_open(srs_utime_t basetime);
|
||||
|
||||
// clang-format off
|
||||
SRS_DECLARE_PRIVATE: // clang-format on
|
||||
virtual std::string generate_m4s_filename();
|
||||
|
||||
public:
|
||||
virtual srs_error_t on_sequence_header();
|
||||
// Whether segment overflow,
|
||||
// that is whether the current segment duration>=(the segment in config)
|
||||
|
|
@ -494,6 +567,7 @@ SRS_DECLARE_PRIVATE: // clang-format on
|
|||
virtual srs_error_t write_hls_key();
|
||||
virtual srs_error_t refresh_m3u8();
|
||||
virtual srs_error_t do_refresh_m3u8(std::string m3u8_file);
|
||||
virtual srs_error_t do_refresh_m3u8_segment(SrsHlsM4sSegment *segment, std::stringstream &ss);
|
||||
};
|
||||
|
||||
// The base class for HLS controller
|
||||
|
|
@ -549,7 +623,7 @@ SRS_DECLARE_PRIVATE: // clang-format on
|
|||
SRS_DECLARE_PRIVATE: // clang-format on
|
||||
// The HLS muxer to reap ts and m3u8.
|
||||
// The TS is cached to SrsTsMessageCache then flush to ts segment.
|
||||
SrsHlsMuxer *muxer_;
|
||||
ISrsHlsMuxer *muxer_;
|
||||
// The TS cache
|
||||
SrsTsMessageCache *tsmc_;
|
||||
|
||||
|
|
|
|||
|
|
@ -1102,22 +1102,26 @@ VOID TEST(AppHlsTest, HlsControllerWriteAudioTypicalScenario)
|
|||
// Create mock request
|
||||
MockHlsRequest mock_request("__defaultVhost__", "live", "test");
|
||||
|
||||
// Cast to concrete type to access private members for testing
|
||||
SrsHlsMuxer *muxer = dynamic_cast<SrsHlsMuxer*>(controller->muxer_);
|
||||
srs_assert(muxer);
|
||||
|
||||
// Set up muxer with required fields
|
||||
controller->muxer_->req_ = &mock_request;
|
||||
controller->muxer_->hls_fragment_ = 10 * SRS_UTIME_SECONDS;
|
||||
controller->muxer_->hls_aof_ratio_ = 2.0;
|
||||
controller->muxer_->hls_wait_keyframe_ = true;
|
||||
controller->muxer_->hls_ts_floor_ = false;
|
||||
controller->muxer_->deviation_ts_ = 0;
|
||||
controller->muxer_->max_td_ = 10 * SRS_UTIME_SECONDS;
|
||||
controller->muxer_->latest_acodec_ = SrsAudioCodecIdAAC;
|
||||
controller->muxer_->latest_vcodec_ = SrsVideoCodecIdDisabled; // Pure audio mode
|
||||
controller->muxer_->writer_ = new MockSrsFileWriter();
|
||||
controller->muxer_->context_ = new SrsTsContext();
|
||||
muxer->req_ = &mock_request;
|
||||
muxer->hls_fragment_ = 10 * SRS_UTIME_SECONDS;
|
||||
muxer->hls_aof_ratio_ = 2.0;
|
||||
muxer->hls_wait_keyframe_ = true;
|
||||
muxer->hls_ts_floor_ = false;
|
||||
muxer->deviation_ts_ = 0;
|
||||
muxer->max_td_ = 10 * SRS_UTIME_SECONDS;
|
||||
muxer->latest_acodec_ = SrsAudioCodecIdAAC;
|
||||
muxer->latest_vcodec_ = SrsVideoCodecIdDisabled; // Pure audio mode
|
||||
muxer->writer_ = new MockSrsFileWriter();
|
||||
muxer->context_ = new SrsTsContext();
|
||||
|
||||
// Create a segment
|
||||
SrsHlsSegment *segment = new SrsHlsSegment(controller->muxer_->context_, SrsAudioCodecIdAAC, SrsVideoCodecIdDisabled, new MockSrsFileWriter());
|
||||
controller->muxer_->current_ = segment;
|
||||
SrsHlsSegment *segment = new SrsHlsSegment(muxer->context_, SrsAudioCodecIdAAC, SrsVideoCodecIdDisabled, new MockSrsFileWriter());
|
||||
muxer->current_ = segment;
|
||||
segment->append(0);
|
||||
|
||||
// Create SrsFormat with AAC audio codec
|
||||
|
|
@ -1197,11 +1201,11 @@ VOID TEST(AppHlsTest, HlsControllerWriteAudioTypicalScenario)
|
|||
EXPECT_GT(controller->aac_samples_, 1024);
|
||||
|
||||
// Clean up
|
||||
controller->muxer_->req_ = NULL;
|
||||
controller->muxer_->current_ = NULL;
|
||||
muxer->req_ = NULL;
|
||||
muxer->current_ = NULL;
|
||||
srs_freep(segment);
|
||||
srs_freep(controller->muxer_->writer_);
|
||||
srs_freep(controller->muxer_->context_);
|
||||
srs_freep(muxer->writer_);
|
||||
srs_freep(muxer->context_);
|
||||
}
|
||||
|
||||
// Unit test for SrsHlsMuxer::flush_video typical scenario
|
||||
|
|
@ -1414,6 +1418,10 @@ VOID TEST(AppHlsTest, HlsControllerSelectionTypicalScenario)
|
|||
// Initialize the controller
|
||||
HELPER_EXPECT_SUCCESS(controller->initialize());
|
||||
|
||||
// Cast to concrete type to access private members for testing
|
||||
SrsHlsMuxer *muxer = dynamic_cast<SrsHlsMuxer*>(controller->muxer_);
|
||||
srs_assert(muxer);
|
||||
|
||||
// Test initial state - no current segment
|
||||
// sequence_no() should return 0
|
||||
EXPECT_EQ(0, controller->sequence_no());
|
||||
|
|
@ -1439,8 +1447,8 @@ VOID TEST(AppHlsTest, HlsControllerSelectionTypicalScenario)
|
|||
segment->append(10000); // 10 seconds duration in milliseconds
|
||||
|
||||
// Set the current segment in the muxer
|
||||
controller->muxer_->current_ = segment;
|
||||
controller->muxer_->sequence_no_ = 42;
|
||||
muxer->current_ = segment;
|
||||
muxer->sequence_no_ = 42;
|
||||
|
||||
// Test selection code with current segment
|
||||
// sequence_no() should return the muxer's sequence number
|
||||
|
|
@ -1456,14 +1464,14 @@ VOID TEST(AppHlsTest, HlsControllerSelectionTypicalScenario)
|
|||
EXPECT_EQ(0, controller->deviation());
|
||||
|
||||
// Test deviation with hls_ts_floor enabled
|
||||
controller->muxer_->hls_ts_floor_ = true;
|
||||
controller->muxer_->deviation_ts_ = 5;
|
||||
muxer->hls_ts_floor_ = true;
|
||||
muxer->deviation_ts_ = 5;
|
||||
|
||||
// deviation() should return the deviation value when hls_ts_floor is true
|
||||
EXPECT_EQ(5, controller->deviation());
|
||||
|
||||
// Clean up
|
||||
controller->muxer_->current_ = NULL;
|
||||
muxer->current_ = NULL;
|
||||
}
|
||||
|
||||
// Unit test for SrsHlsController::on_publish typical scenario
|
||||
|
|
@ -1481,6 +1489,10 @@ VOID TEST(AppHlsTest, HlsControllerOnPublishTypicalScenario)
|
|||
// Initialize the controller
|
||||
HELPER_EXPECT_SUCCESS(controller->initialize());
|
||||
|
||||
// Cast to concrete type to access private members for testing
|
||||
SrsHlsMuxer *muxer = dynamic_cast<SrsHlsMuxer*>(controller->muxer_);
|
||||
srs_assert(muxer);
|
||||
|
||||
// Create mock request
|
||||
MockHlsRequest mock_request("test.vhost", "live", "stream1");
|
||||
|
||||
|
|
@ -1489,47 +1501,47 @@ VOID TEST(AppHlsTest, HlsControllerOnPublishTypicalScenario)
|
|||
|
||||
// Verify that muxer was configured properly
|
||||
// Check that muxer's request was set
|
||||
EXPECT_TRUE(controller->muxer_->req_ != NULL);
|
||||
EXPECT_EQ("test.vhost", controller->muxer_->req_->vhost_);
|
||||
EXPECT_EQ("live", controller->muxer_->req_->app_);
|
||||
EXPECT_EQ("stream1", controller->muxer_->req_->stream_);
|
||||
EXPECT_TRUE(muxer->req_ != NULL);
|
||||
EXPECT_EQ("test.vhost", muxer->req_->vhost_);
|
||||
EXPECT_EQ("live", muxer->req_->app_);
|
||||
EXPECT_EQ("stream1", muxer->req_->stream_);
|
||||
|
||||
// Verify HLS configuration was applied to muxer
|
||||
// Fragment should be 10 seconds (from MockAppConfig default)
|
||||
EXPECT_EQ(10 * SRS_UTIME_SECONDS, controller->muxer_->hls_fragment_);
|
||||
EXPECT_EQ(10 * SRS_UTIME_SECONDS, muxer->hls_fragment_);
|
||||
|
||||
// Window should be 60 seconds (from MockAppConfig default)
|
||||
EXPECT_EQ(60 * SRS_UTIME_SECONDS, controller->muxer_->hls_window_);
|
||||
EXPECT_EQ(60 * SRS_UTIME_SECONDS, muxer->hls_window_);
|
||||
|
||||
// Path should be "./objs/nginx/html" (from MockAppConfig default)
|
||||
EXPECT_EQ("./objs/nginx/html", controller->muxer_->hls_path_);
|
||||
EXPECT_EQ("./objs/nginx/html", muxer->hls_path_);
|
||||
|
||||
// TS file should be "[app]/[stream]-[seq].ts" (from MockAppConfig default)
|
||||
EXPECT_EQ("[app]/[stream]-[seq].ts", controller->muxer_->hls_ts_file_);
|
||||
EXPECT_EQ("[app]/[stream]-[seq].ts", muxer->hls_ts_file_);
|
||||
|
||||
// AOF ratio should be 2.0 (from MockAppConfig default)
|
||||
EXPECT_EQ(2.0, controller->muxer_->hls_aof_ratio_);
|
||||
EXPECT_EQ(2.0, muxer->hls_aof_ratio_);
|
||||
|
||||
// Cleanup should be true (from MockAppConfig default)
|
||||
EXPECT_TRUE(controller->muxer_->hls_cleanup_);
|
||||
EXPECT_TRUE(muxer->hls_cleanup_);
|
||||
|
||||
// Wait keyframe should be true (from MockAppConfig default)
|
||||
EXPECT_TRUE(controller->muxer_->hls_wait_keyframe_);
|
||||
EXPECT_TRUE(muxer->hls_wait_keyframe_);
|
||||
|
||||
// TS floor should be false (from MockAppConfig default)
|
||||
EXPECT_FALSE(controller->muxer_->hls_ts_floor_);
|
||||
EXPECT_FALSE(muxer->hls_ts_floor_);
|
||||
|
||||
// Keys should be false (from MockAppConfig default)
|
||||
EXPECT_FALSE(controller->muxer_->hls_keys_);
|
||||
EXPECT_FALSE(muxer->hls_keys_);
|
||||
|
||||
// Fragments per key should be 5 (from MockAppConfig default)
|
||||
EXPECT_EQ(5, controller->muxer_->hls_fragments_per_key_);
|
||||
EXPECT_EQ(5, muxer->hls_fragments_per_key_);
|
||||
|
||||
// Verify hls_dts_directly was set from config
|
||||
EXPECT_TRUE(controller->hls_dts_directly_);
|
||||
|
||||
// Verify that a segment was opened
|
||||
EXPECT_TRUE(controller->muxer_->current_ != NULL);
|
||||
EXPECT_TRUE(muxer->current_ != NULL);
|
||||
}
|
||||
|
||||
// Unit test for SrsHlsController::on_unpublish typical scenario
|
||||
|
|
@ -1547,6 +1559,10 @@ VOID TEST(AppHlsTest, HlsControllerOnUnpublishTypicalScenario)
|
|||
// Initialize the controller
|
||||
HELPER_EXPECT_SUCCESS(controller->initialize());
|
||||
|
||||
// Cast to concrete type to access private members for testing
|
||||
SrsHlsMuxer *muxer = dynamic_cast<SrsHlsMuxer*>(controller->muxer_);
|
||||
srs_assert(muxer);
|
||||
|
||||
// Create mock request
|
||||
MockHlsRequest mock_request("test.vhost", "live", "stream1");
|
||||
|
||||
|
|
@ -1554,7 +1570,7 @@ VOID TEST(AppHlsTest, HlsControllerOnUnpublishTypicalScenario)
|
|||
HELPER_EXPECT_SUCCESS(controller->on_publish(&mock_request));
|
||||
|
||||
// Verify that a segment was opened
|
||||
EXPECT_TRUE(controller->muxer_->current_ != NULL);
|
||||
EXPECT_TRUE(muxer->current_ != NULL);
|
||||
|
||||
// Set the codec in the muxer to enable proper audio/video handling
|
||||
controller->muxer_->set_latest_acodec(SrsAudioCodecIdAAC);
|
||||
|
|
@ -1581,7 +1597,7 @@ VOID TEST(AppHlsTest, HlsControllerOnUnpublishTypicalScenario)
|
|||
EXPECT_TRUE(controller->tsmc_->audio_ == NULL);
|
||||
|
||||
// Verify that the segment was closed (current_ should be NULL after close)
|
||||
EXPECT_TRUE(controller->muxer_->current_ == NULL);
|
||||
EXPECT_TRUE(muxer->current_ == NULL);
|
||||
}
|
||||
|
||||
// Unit test for SrsHlsController::write_video typical scenario
|
||||
|
|
@ -1599,6 +1615,10 @@ VOID TEST(AppHlsTest, HlsControllerWriteVideoTypicalScenario)
|
|||
// Initialize the controller
|
||||
HELPER_EXPECT_SUCCESS(controller->initialize());
|
||||
|
||||
// Cast to concrete type to access private members for testing
|
||||
SrsHlsMuxer *muxer = dynamic_cast<SrsHlsMuxer*>(controller->muxer_);
|
||||
srs_assert(muxer);
|
||||
|
||||
// Create mock request
|
||||
MockHlsRequest mock_request("test.vhost", "live", "stream1");
|
||||
|
||||
|
|
@ -1606,7 +1626,7 @@ VOID TEST(AppHlsTest, HlsControllerWriteVideoTypicalScenario)
|
|||
HELPER_EXPECT_SUCCESS(controller->on_publish(&mock_request));
|
||||
|
||||
// Verify that a segment was opened
|
||||
EXPECT_TRUE(controller->muxer_->current_ != NULL);
|
||||
EXPECT_TRUE(muxer->current_ != NULL);
|
||||
|
||||
// Create a mock SrsFormat with video codec
|
||||
SrsUniquePtr<SrsFormat> format(new SrsFormat());
|
||||
|
|
@ -1649,10 +1669,10 @@ VOID TEST(AppHlsTest, HlsControllerWriteVideoTypicalScenario)
|
|||
EXPECT_TRUE(controller->tsmc_->video_ == NULL);
|
||||
|
||||
// Verify that the segment is still open (not reaped yet, since segment is not overflow)
|
||||
EXPECT_TRUE(controller->muxer_->current_ != NULL);
|
||||
EXPECT_TRUE(muxer->current_ != NULL);
|
||||
|
||||
// Verify that the codec was set correctly
|
||||
EXPECT_EQ(SrsVideoCodecIdAVC, controller->muxer_->latest_vcodec());
|
||||
EXPECT_EQ(SrsVideoCodecIdAVC, muxer->latest_vcodec());
|
||||
}
|
||||
|
||||
// Unit test for SrsHlsController::reap_segment typical scenario
|
||||
|
|
|
|||
|
|
@ -11,6 +11,15 @@
|
|||
#include <srs_protocol_sdp.hpp>
|
||||
#include <srs_kernel_packet.hpp>
|
||||
#include <srs_kernel_codec.hpp>
|
||||
#include <srs_app_hls.hpp>
|
||||
#include <srs_utest_manual_mock.hpp>
|
||||
#include <srs_utest_manual_kernel.hpp>
|
||||
#include <srs_app_config.hpp>
|
||||
#include <srs_app_http_hooks.hpp>
|
||||
#include <srs_app_utility.hpp>
|
||||
#include <srs_kernel_utility.hpp>
|
||||
#include <srs_protocol_utility.hpp>
|
||||
#include <srs_app_fragment.hpp>
|
||||
|
||||
#ifdef SRS_FFMPEG_FIT
|
||||
#include <srs_app_rtc_codec.hpp>
|
||||
|
|
@ -469,3 +478,554 @@ VOID TEST(FFmpegLogHelperTest, LogCallback)
|
|||
EXPECT_TRUE(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Test SrsDvrAsyncCallOnHls::call() method
|
||||
VOID TEST(DvrAsyncCallOnHlsTest, CallWithMultipleHooks)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
||||
// Create mock config with HTTP hooks enabled
|
||||
MockAppConfig mock_config;
|
||||
mock_config.http_hooks_enabled_ = true;
|
||||
|
||||
// Create on_hls directive with multiple hook URLs
|
||||
mock_config.on_hls_directive_ = new SrsConfDirective();
|
||||
mock_config.on_hls_directive_->name_ = "on_hls";
|
||||
mock_config.on_hls_directive_->args_.push_back("http://example.com/hook1");
|
||||
mock_config.on_hls_directive_->args_.push_back("http://example.com/hook2");
|
||||
|
||||
// Create mock hooks
|
||||
MockHttpHooks mock_hooks;
|
||||
|
||||
// Create mock request
|
||||
MockRequest mock_req("test_vhost", "live", "stream");
|
||||
|
||||
// Create SrsDvrAsyncCallOnHls instance
|
||||
SrsContextId cid;
|
||||
SrsDvrAsyncCallOnHls call(cid, &mock_req, "/path/to/file.ts", "http://example.com/file.ts",
|
||||
"m3u8_content", "http://example.com/playlist.m3u8", 1, 10 * SRS_UTIME_SECONDS);
|
||||
|
||||
// Replace global config and hooks with mocks
|
||||
call.config_ = &mock_config;
|
||||
call.hooks_ = &mock_hooks;
|
||||
|
||||
// Call should succeed and invoke hooks for each URL
|
||||
HELPER_EXPECT_SUCCESS(call.call());
|
||||
}
|
||||
|
||||
// Mock HLS muxer for testing SrsHlsController::reap_segment
|
||||
class MockHlsMuxerForReapSegment : public ISrsHlsMuxer
|
||||
{
|
||||
SRS_DECLARE_PRIVATE:
|
||||
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:
|
||||
MockHlsMuxerForReapSegment()
|
||||
{
|
||||
segment_close_count_ = 0;
|
||||
segment_open_count_ = 0;
|
||||
flush_video_count_ = 0;
|
||||
flush_audio_count_ = 0;
|
||||
segment_close_error_ = srs_success;
|
||||
segment_open_error_ = srs_success;
|
||||
flush_video_error_ = srs_success;
|
||||
flush_audio_error_ = srs_success;
|
||||
}
|
||||
|
||||
virtual ~MockHlsMuxerForReapSegment()
|
||||
{
|
||||
srs_freep(segment_close_error_);
|
||||
srs_freep(segment_open_error_);
|
||||
srs_freep(flush_video_error_);
|
||||
srs_freep(flush_audio_error_);
|
||||
}
|
||||
|
||||
// ISrsHlsMuxer interface - only implement methods used by reap_segment
|
||||
virtual srs_error_t initialize() { return srs_success; }
|
||||
virtual void dispose() {}
|
||||
virtual int sequence_no() { return 0; }
|
||||
virtual std::string ts_url() { return ""; }
|
||||
virtual srs_utime_t duration() { return 0; }
|
||||
virtual int deviation() { return 0; }
|
||||
virtual SrsAudioCodecId latest_acodec() { return SrsAudioCodecIdForbidden; }
|
||||
virtual void set_latest_acodec(SrsAudioCodecId v) {}
|
||||
virtual SrsVideoCodecId latest_vcodec() { return SrsVideoCodecIdForbidden; }
|
||||
virtual void set_latest_vcodec(SrsVideoCodecId v) {}
|
||||
virtual bool pure_audio() { return false; }
|
||||
virtual bool is_segment_overflow() { return false; }
|
||||
virtual bool is_segment_absolutely_overflow() { return false; }
|
||||
virtual bool wait_keyframe() { return false; }
|
||||
virtual srs_error_t on_publish(ISrsRequest *req) { return srs_success; }
|
||||
virtual srs_error_t on_unpublish() { return srs_success; }
|
||||
virtual srs_error_t update_config(ISrsRequest *r, std::string entry_prefix,
|
||||
std::string path, std::string m3u8_file, std::string ts_file,
|
||||
srs_utime_t fragment, srs_utime_t window, bool ts_floor, double aof_ratio,
|
||||
bool cleanup, bool wait_keyframe, bool keys, int fragments_per_key,
|
||||
std::string key_file, std::string key_file_path, std::string key_url)
|
||||
{
|
||||
return srs_success;
|
||||
}
|
||||
virtual srs_error_t on_sequence_header() { return srs_success; }
|
||||
virtual void update_duration(uint64_t dts) {}
|
||||
virtual srs_error_t recover_hls() { return srs_success; }
|
||||
|
||||
// Methods used by reap_segment
|
||||
virtual srs_error_t segment_close()
|
||||
{
|
||||
segment_close_count_++;
|
||||
return srs_error_copy(segment_close_error_);
|
||||
}
|
||||
|
||||
virtual srs_error_t segment_open()
|
||||
{
|
||||
segment_open_count_++;
|
||||
return srs_error_copy(segment_open_error_);
|
||||
}
|
||||
|
||||
virtual srs_error_t flush_video(SrsTsMessageCache *cache)
|
||||
{
|
||||
flush_video_count_++;
|
||||
return srs_error_copy(flush_video_error_);
|
||||
}
|
||||
|
||||
virtual srs_error_t flush_audio(SrsTsMessageCache *cache)
|
||||
{
|
||||
flush_audio_count_++;
|
||||
return srs_error_copy(flush_audio_error_);
|
||||
}
|
||||
|
||||
// Test helpers
|
||||
void set_segment_close_error(srs_error_t err)
|
||||
{
|
||||
srs_freep(segment_close_error_);
|
||||
segment_close_error_ = srs_error_copy(err);
|
||||
}
|
||||
|
||||
void set_segment_open_error(srs_error_t err)
|
||||
{
|
||||
srs_freep(segment_open_error_);
|
||||
segment_open_error_ = srs_error_copy(err);
|
||||
}
|
||||
|
||||
void set_flush_video_error(srs_error_t err)
|
||||
{
|
||||
srs_freep(flush_video_error_);
|
||||
flush_video_error_ = srs_error_copy(err);
|
||||
}
|
||||
|
||||
void set_flush_audio_error(srs_error_t err)
|
||||
{
|
||||
srs_freep(flush_audio_error_);
|
||||
flush_audio_error_ = srs_error_copy(err);
|
||||
}
|
||||
|
||||
int get_segment_close_count() const { return segment_close_count_; }
|
||||
int get_segment_open_count() const { return segment_open_count_; }
|
||||
int get_flush_video_count() const { return flush_video_count_; }
|
||||
int get_flush_audio_count() const { return flush_audio_count_; }
|
||||
};
|
||||
|
||||
// Test: SrsHlsController::reap_segment success path
|
||||
VOID TEST(HlsControllerTest, ReapSegmentSuccess)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
||||
// Create controller
|
||||
SrsHlsController controller;
|
||||
|
||||
// Replace muxer with mock
|
||||
MockHlsMuxerForReapSegment *mock_muxer = new MockHlsMuxerForReapSegment();
|
||||
srs_freep(controller.muxer_);
|
||||
controller.muxer_ = mock_muxer;
|
||||
|
||||
// Call reap_segment - should succeed
|
||||
HELPER_EXPECT_SUCCESS(controller.reap_segment());
|
||||
|
||||
// Verify the sequence of operations
|
||||
EXPECT_EQ(1, mock_muxer->get_segment_close_count());
|
||||
EXPECT_EQ(1, mock_muxer->get_segment_open_count());
|
||||
EXPECT_EQ(1, mock_muxer->get_flush_video_count());
|
||||
EXPECT_EQ(1, mock_muxer->get_flush_audio_count());
|
||||
}
|
||||
|
||||
// Mock HLS segment for testing do_segment_close
|
||||
class MockHlsSegmentForSegmentClose : public SrsHlsSegment
|
||||
{
|
||||
SRS_DECLARE_PRIVATE:
|
||||
srs_error_t rename_error_;
|
||||
srs_utime_t mock_duration_;
|
||||
bool rename_called_;
|
||||
|
||||
public:
|
||||
MockHlsSegmentForSegmentClose() : SrsHlsSegment(NULL, SrsAudioCodecIdAAC, SrsVideoCodecIdAVC, NULL)
|
||||
{
|
||||
rename_error_ = srs_success;
|
||||
mock_duration_ = 10 * SRS_UTIME_SECONDS; // Default 10 seconds
|
||||
rename_called_ = false;
|
||||
sequence_no_ = 1;
|
||||
uri_ = "segment-1.ts";
|
||||
// tscw_ is already NULL from base class, leave it NULL
|
||||
}
|
||||
|
||||
virtual ~MockHlsSegmentForSegmentClose()
|
||||
{
|
||||
srs_freep(rename_error_);
|
||||
}
|
||||
|
||||
virtual srs_error_t rename()
|
||||
{
|
||||
rename_called_ = true;
|
||||
return srs_error_copy(rename_error_);
|
||||
}
|
||||
|
||||
virtual srs_utime_t duration()
|
||||
{
|
||||
return mock_duration_;
|
||||
}
|
||||
|
||||
void set_rename_error(srs_error_t err)
|
||||
{
|
||||
srs_freep(rename_error_);
|
||||
rename_error_ = srs_error_copy(err);
|
||||
}
|
||||
|
||||
void set_duration(srs_utime_t dur)
|
||||
{
|
||||
mock_duration_ = dur;
|
||||
}
|
||||
|
||||
bool is_rename_called() const
|
||||
{
|
||||
return rename_called_;
|
||||
}
|
||||
};
|
||||
|
||||
// Test: SrsHlsMuxer::do_segment_close2 success path with valid duration
|
||||
VOID TEST(HlsMuxerTest, DoSegmentCloseSuccess)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
||||
// Create HLS muxer
|
||||
SrsHlsMuxer muxer;
|
||||
HELPER_EXPECT_SUCCESS(muxer.initialize());
|
||||
|
||||
// Create mock request
|
||||
MockRequest req("test_vhost", "live", "stream");
|
||||
muxer.req_ = &req;
|
||||
|
||||
// Create mock segment with valid duration (10 seconds)
|
||||
MockHlsSegmentForSegmentClose *mock_segment = new MockHlsSegmentForSegmentClose();
|
||||
mock_segment->set_duration(10 * SRS_UTIME_SECONDS);
|
||||
muxer.current_ = mock_segment;
|
||||
|
||||
// Set max_td_ to 10 seconds (fragment duration)
|
||||
muxer.max_td_ = 10 * SRS_UTIME_SECONDS;
|
||||
|
||||
// Call do_segment_close2 - should succeed
|
||||
HELPER_EXPECT_SUCCESS(muxer.do_segment_close2());
|
||||
|
||||
// Verify segment was renamed
|
||||
EXPECT_TRUE(mock_segment->is_rename_called());
|
||||
|
||||
// Verify segment was added to segments window
|
||||
EXPECT_EQ(1, muxer.segments_->size());
|
||||
|
||||
// Verify current_ is set to NULL
|
||||
EXPECT_TRUE(muxer.current_ == NULL);
|
||||
|
||||
// Cleanup
|
||||
muxer.req_ = NULL;
|
||||
}
|
||||
|
||||
// Test: SrsHlsMuxer::generate_ts_filename with hls_ts_floor enabled
|
||||
VOID TEST(HlsMuxerTest, GenerateTsFilenameWithFloor)
|
||||
{
|
||||
// Create HLS muxer
|
||||
SrsHlsMuxer muxer;
|
||||
|
||||
// Create mock request
|
||||
MockRequest req("test_vhost", "live", "stream");
|
||||
muxer.req_ = &req;
|
||||
|
||||
// Set up muxer configuration with ts_floor enabled
|
||||
muxer.hls_ts_file_ = "[vhost]/[app]/[stream]-[timestamp]-[seq].ts";
|
||||
muxer.hls_ts_floor_ = true;
|
||||
muxer.hls_fragment_ = 10 * SRS_UTIME_SECONDS;
|
||||
muxer.accept_floor_ts_ = 0;
|
||||
muxer.previous_floor_ts_ = 0;
|
||||
muxer.deviation_ts_ = 0;
|
||||
|
||||
// Create a mock segment with sequence number
|
||||
SrsHlsSegment *segment = new SrsHlsSegment(muxer.context_, SrsAudioCodecIdAAC, SrsVideoCodecIdDisabled, new MockSrsFileWriter());
|
||||
segment->sequence_no_ = 100;
|
||||
muxer.current_ = segment;
|
||||
|
||||
// Call generate_ts_filename
|
||||
std::string ts_filename = muxer.generate_ts_filename();
|
||||
|
||||
// Verify the filename contains replaced variables
|
||||
EXPECT_TRUE(ts_filename.find("test_vhost") != std::string::npos);
|
||||
EXPECT_TRUE(ts_filename.find("live") != std::string::npos);
|
||||
EXPECT_TRUE(ts_filename.find("stream") != std::string::npos);
|
||||
EXPECT_TRUE(ts_filename.find("100") != std::string::npos); // sequence number
|
||||
|
||||
// Verify accept_floor_ts_ was initialized (should be current_floor_ts - 1 on first call)
|
||||
EXPECT_TRUE(muxer.accept_floor_ts_ > 0);
|
||||
|
||||
// Verify previous_floor_ts_ was set
|
||||
EXPECT_TRUE(muxer.previous_floor_ts_ > 0);
|
||||
|
||||
// Verify deviation_ts_ was calculated
|
||||
EXPECT_TRUE(muxer.deviation_ts_ <= 0); // Should be negative or zero since accept_floor_ts_ starts at current_floor_ts - 1
|
||||
|
||||
// Call again to test the increment logic
|
||||
int64_t first_accept_floor_ts = muxer.accept_floor_ts_;
|
||||
segment->sequence_no_ = 101;
|
||||
std::string ts_filename2 = muxer.generate_ts_filename();
|
||||
|
||||
// Verify accept_floor_ts_ was incremented
|
||||
EXPECT_EQ(first_accept_floor_ts + 1, muxer.accept_floor_ts_);
|
||||
|
||||
// Verify sequence number was replaced
|
||||
EXPECT_TRUE(ts_filename2.find("101") != std::string::npos);
|
||||
|
||||
// Cleanup
|
||||
muxer.req_ = NULL;
|
||||
muxer.current_ = NULL;
|
||||
srs_freep(segment);
|
||||
}
|
||||
|
||||
// Mock segment for testing do_refresh_m3u8_segment
|
||||
class MockHlsM4sSegment : public SrsHlsM4sSegment
|
||||
{
|
||||
public:
|
||||
bool is_sequence_header_;
|
||||
srs_utime_t duration_;
|
||||
std::string fullpath_;
|
||||
|
||||
MockHlsM4sSegment() : SrsHlsM4sSegment(NULL)
|
||||
{
|
||||
is_sequence_header_ = false;
|
||||
duration_ = 5000 * SRS_UTIME_MILLISECONDS; // 5 seconds
|
||||
fullpath_ = "/path/to/segment-[duration].m4s";
|
||||
sequence_no_ = 0;
|
||||
memset(iv_, 0, 16);
|
||||
// Set a test IV value
|
||||
for (int i = 0; i < 16; i++) {
|
||||
iv_[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~MockHlsM4sSegment() {}
|
||||
|
||||
virtual bool is_sequence_header() { return is_sequence_header_; }
|
||||
virtual srs_utime_t duration() { return duration_; }
|
||||
virtual std::string fullpath() { return fullpath_; }
|
||||
};
|
||||
|
||||
// Test: do_refresh_m3u8_segment with encryption enabled
|
||||
VOID TEST(HlsFmp4MuxerTest, DoRefreshM3u8SegmentWithEncryption)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
||||
// Create muxer and set up encryption
|
||||
SrsHlsFmp4Muxer muxer;
|
||||
|
||||
// Set up request
|
||||
MockRequest req("test_vhost", "test_app", "test_stream");
|
||||
muxer.req_ = &req;
|
||||
|
||||
// Enable encryption
|
||||
muxer.hls_keys_ = true;
|
||||
muxer.hls_fragments_per_key_ = 5;
|
||||
muxer.hls_key_file_ = "key-[seq].key";
|
||||
muxer.hls_key_url_ = "https://example.com/keys/";
|
||||
|
||||
// Create mock segment
|
||||
MockHlsM4sSegment segment;
|
||||
segment.sequence_no_ = 10; // 10 % 5 == 0, so key should be written
|
||||
segment.is_sequence_header_ = true; // Should write discontinuity
|
||||
segment.duration_ = 5000 * SRS_UTIME_MILLISECONDS; // 5 seconds
|
||||
segment.fullpath_ = "/path/to/segment-[duration].m4s";
|
||||
|
||||
// Call do_refresh_m3u8_segment
|
||||
std::stringstream ss;
|
||||
HELPER_EXPECT_SUCCESS(muxer.do_refresh_m3u8_segment(&segment, ss));
|
||||
|
||||
// Verify output
|
||||
std::string output = ss.str();
|
||||
|
||||
// Should contain discontinuity tag
|
||||
EXPECT_TRUE(output.find("#EXT-X-DISCONTINUITY") != std::string::npos);
|
||||
|
||||
// Should contain encryption key tag
|
||||
EXPECT_TRUE(output.find("#EXT-X-KEY:METHOD=SAMPLE-AES") != std::string::npos);
|
||||
EXPECT_TRUE(output.find("https://example.com/keys/") != std::string::npos);
|
||||
EXPECT_TRUE(output.find("key-10.key") != std::string::npos);
|
||||
EXPECT_TRUE(output.find("IV=0x") != std::string::npos);
|
||||
|
||||
// Should contain EXTINF tag with duration
|
||||
EXPECT_TRUE(output.find("#EXTINF:5.000") != std::string::npos);
|
||||
|
||||
// Should contain segment filename
|
||||
EXPECT_TRUE(output.find("segment-5000.m4s") != std::string::npos);
|
||||
|
||||
// Cleanup
|
||||
muxer.req_ = NULL;
|
||||
}
|
||||
|
||||
// Mock HLS segment for testing SrsHlsMuxer::do_refresh_m3u8_segment
|
||||
class MockHlsSegmentForRefreshM3u8 : public SrsHlsSegment
|
||||
{
|
||||
SRS_DECLARE_PRIVATE:
|
||||
bool is_sequence_header_;
|
||||
srs_utime_t duration_;
|
||||
|
||||
public:
|
||||
MockHlsSegmentForRefreshM3u8() : SrsHlsSegment(NULL, SrsAudioCodecIdAAC, SrsVideoCodecIdAVC, NULL)
|
||||
{
|
||||
is_sequence_header_ = false;
|
||||
duration_ = 5000 * SRS_UTIME_MILLISECONDS; // 5 seconds
|
||||
sequence_no_ = 0;
|
||||
uri_ = "segment-[duration].ts";
|
||||
// Set a test IV value
|
||||
for (int i = 0; i < 16; i++) {
|
||||
iv_[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~MockHlsSegmentForRefreshM3u8() {}
|
||||
|
||||
virtual bool is_sequence_header() { return is_sequence_header_; }
|
||||
virtual srs_utime_t duration() { return duration_; }
|
||||
|
||||
void set_is_sequence_header(bool v) { is_sequence_header_ = v; }
|
||||
void set_duration(srs_utime_t dur) { duration_ = dur; }
|
||||
};
|
||||
|
||||
// Test: SrsHlsMuxer::do_refresh_m3u8_segment with encryption enabled
|
||||
VOID TEST(HlsMuxerTest, DoRefreshM3u8SegmentWithEncryption)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
||||
// Create muxer
|
||||
SrsHlsMuxer muxer;
|
||||
|
||||
// Set up request
|
||||
MockRequest req("test_vhost", "test_app", "test_stream");
|
||||
muxer.req_ = &req;
|
||||
|
||||
// Enable encryption
|
||||
muxer.hls_keys_ = true;
|
||||
muxer.hls_fragments_per_key_ = 5;
|
||||
muxer.hls_key_file_ = "key-[seq].key";
|
||||
muxer.hls_key_url_ = "https://example.com/keys/";
|
||||
|
||||
// Create mock segment
|
||||
MockHlsSegmentForRefreshM3u8 segment;
|
||||
segment.sequence_no_ = 10; // 10 % 5 == 0, so key should be written
|
||||
segment.set_is_sequence_header(true); // Should write discontinuity
|
||||
segment.set_duration(5000 * SRS_UTIME_MILLISECONDS); // 5 seconds
|
||||
|
||||
// Call do_refresh_m3u8_segment
|
||||
std::stringstream ss;
|
||||
HELPER_EXPECT_SUCCESS(muxer.do_refresh_m3u8_segment(&segment, ss));
|
||||
|
||||
// Verify output
|
||||
std::string output = ss.str();
|
||||
|
||||
// Should contain discontinuity tag
|
||||
EXPECT_TRUE(output.find("#EXT-X-DISCONTINUITY") != std::string::npos);
|
||||
|
||||
// Should contain encryption key tag with AES-128 method
|
||||
EXPECT_TRUE(output.find("#EXT-X-KEY:METHOD=AES-128") != std::string::npos);
|
||||
EXPECT_TRUE(output.find("https://example.com/keys/") != std::string::npos);
|
||||
EXPECT_TRUE(output.find("key-10.key") != std::string::npos);
|
||||
EXPECT_TRUE(output.find("IV=0x") != std::string::npos);
|
||||
|
||||
// Should contain EXTINF tag with duration
|
||||
EXPECT_TRUE(output.find("#EXTINF:5.000") != std::string::npos);
|
||||
|
||||
// Should contain segment filename with duration replaced
|
||||
EXPECT_TRUE(output.find("segment-5000.ts") != std::string::npos);
|
||||
|
||||
// Cleanup
|
||||
muxer.req_ = NULL;
|
||||
}
|
||||
|
||||
// Mock segment for testing SrsHlsFmp4Muxer::generate_m4s_filename
|
||||
class MockHlsM4sSegmentForFilename : public SrsHlsM4sSegment
|
||||
{
|
||||
public:
|
||||
MockHlsM4sSegmentForFilename() : SrsHlsM4sSegment(NULL)
|
||||
{
|
||||
sequence_no_ = 0;
|
||||
}
|
||||
|
||||
virtual ~MockHlsM4sSegmentForFilename() {}
|
||||
};
|
||||
|
||||
// Test: SrsHlsFmp4Muxer::generate_m4s_filename with hls_ts_floor enabled
|
||||
VOID TEST(HlsFmp4MuxerTest, GenerateM4sFilenameWithFloor)
|
||||
{
|
||||
// Create HLS fmp4 muxer
|
||||
SrsHlsFmp4Muxer muxer;
|
||||
|
||||
// Create mock request
|
||||
MockRequest req("test_vhost", "live", "stream");
|
||||
muxer.req_ = &req;
|
||||
|
||||
// Set up muxer configuration with ts_floor enabled
|
||||
muxer.hls_m4s_file_ = "[vhost]/[app]/[stream]-[timestamp]-[seq].m4s";
|
||||
muxer.hls_ts_floor_ = true;
|
||||
muxer.hls_fragment_ = 10 * SRS_UTIME_SECONDS;
|
||||
muxer.accept_floor_ts_ = 0;
|
||||
muxer.previous_floor_ts_ = 0;
|
||||
muxer.deviation_ts_ = 0;
|
||||
|
||||
// Create a mock segment with sequence number
|
||||
MockHlsM4sSegmentForFilename *segment = new MockHlsM4sSegmentForFilename();
|
||||
segment->sequence_no_ = 100;
|
||||
muxer.current_ = segment;
|
||||
|
||||
// Call generate_m4s_filename
|
||||
std::string m4s_filename = muxer.generate_m4s_filename();
|
||||
|
||||
// Verify the filename contains replaced variables
|
||||
EXPECT_TRUE(m4s_filename.find("test_vhost") != std::string::npos);
|
||||
EXPECT_TRUE(m4s_filename.find("live") != std::string::npos);
|
||||
EXPECT_TRUE(m4s_filename.find("stream") != std::string::npos);
|
||||
EXPECT_TRUE(m4s_filename.find("100") != std::string::npos); // sequence number
|
||||
|
||||
// Verify accept_floor_ts_ was initialized (should be current_floor_ts - 1 on first call)
|
||||
EXPECT_TRUE(muxer.accept_floor_ts_ > 0);
|
||||
|
||||
// Verify previous_floor_ts_ was set
|
||||
EXPECT_TRUE(muxer.previous_floor_ts_ > 0);
|
||||
|
||||
// Verify deviation_ts_ was calculated
|
||||
EXPECT_TRUE(muxer.deviation_ts_ <= 0); // Should be negative or zero since accept_floor_ts_ starts at current_floor_ts - 1
|
||||
|
||||
// Call again to test the increment logic
|
||||
int64_t first_accept_floor_ts = muxer.accept_floor_ts_;
|
||||
segment->sequence_no_ = 101;
|
||||
std::string m4s_filename2 = muxer.generate_m4s_filename();
|
||||
|
||||
// Verify accept_floor_ts_ was incremented
|
||||
EXPECT_EQ(first_accept_floor_ts + 1, muxer.accept_floor_ts_);
|
||||
|
||||
// Verify sequence number was replaced
|
||||
EXPECT_TRUE(m4s_filename2.find("101") != std::string::npos);
|
||||
|
||||
// Cleanup
|
||||
muxer.req_ = NULL;
|
||||
muxer.current_ = NULL;
|
||||
srs_freep(segment);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,6 +270,7 @@ public:
|
|||
bool http_hooks_enabled_;
|
||||
SrsConfDirective *on_stop_directive_;
|
||||
SrsConfDirective *on_unpublish_directive_;
|
||||
SrsConfDirective *on_hls_directive_;
|
||||
bool rtc_nack_enabled_;
|
||||
bool rtc_nack_no_copy_;
|
||||
int rtc_drop_for_pt_;
|
||||
|
|
@ -299,6 +300,7 @@ public:
|
|||
http_hooks_enabled_ = true;
|
||||
on_stop_directive_ = NULL;
|
||||
on_unpublish_directive_ = NULL;
|
||||
on_hls_directive_ = NULL;
|
||||
rtc_nack_enabled_ = true;
|
||||
rtc_nack_no_copy_ = false;
|
||||
rtc_drop_for_pt_ = 0;
|
||||
|
|
@ -330,6 +332,7 @@ public:
|
|||
srs_freep(default_vhost_);
|
||||
srs_freep(forwards_directive_);
|
||||
srs_freep(backend_directive_);
|
||||
srs_freep(on_hls_directive_);
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -501,7 +504,7 @@ public:
|
|||
virtual bool get_rtc_stun_strict_check(std::string vhost) { return false; }
|
||||
virtual std::string get_rtc_dtls_role(std::string vhost) { return rtc_dtls_role_; }
|
||||
virtual std::string get_rtc_dtls_version(std::string vhost) { return "auto"; }
|
||||
virtual SrsConfDirective *get_vhost_on_hls(std::string vhost) { return NULL; }
|
||||
virtual SrsConfDirective *get_vhost_on_hls(std::string vhost) { return on_hls_directive_; }
|
||||
virtual SrsConfDirective *get_vhost_on_hls_notify(std::string vhost) { return NULL; }
|
||||
// HLS methods
|
||||
virtual bool get_hls_enabled(std::string vhost) { return false; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user