diff --git a/Dockerfile b/Dockerfile index 77d5346529..98e6a8e11a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,5 +57,5 @@ RUN ldd /usr/local/srs/objs/ffmpeg/bin/ffmpeg && \ # Default workdir and command. WORKDIR /usr/local/srs ENV SRS_DAEMON=off -CMD ["./objs/srs", "-c", "conf/srs.conf"] +CMD ["./objs/srs", "-c", "conf/docker.conf"] diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index ed4a756aa3..53ceeecad0 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -1747,27 +1747,6 @@ vhost hls.srs.com { # default: off enabled on; - # Whether enable hls_ctx for HLS streaming, for which we create a "fake" connection for HTTP API and callback. - # For each HLS streaming session, we use a child m3u8 with a session identified by query "hls_ctx", it simply - # work as the session id. - # Once the HLS streaming session is created, we will cleanup it when timeout in 2*hls_window seconds. So it - # takes a long time period to identify the timeout. - # Now we got a HLS stremaing session, just like RTMP/WebRTC/HTTP-FLV streaming, we're able to stat the session - # as a "fake" connection, do HTTP callback when start playing the HLS streaming. You're able to do querying and - # authentication. - # Note that it will make NGINX edge cache always missed, so never enable HLS streaming if use NGINX edges. - # Overwrite by env SRS_VHOST_HLS_HLS_CTX for all vhosts. - # Default: on - hls_ctx on; - # For HLS pseudo streaming, whether enable the session for each TS segment. - # If enabled, SRS HTTP API will show the statistics about HLS streaming bandwidth, both m3u8 and ts file. Please - # note that it also consumes resource, because each ts file should be served by SRS, all NGINX cache will be - # missed because we add session id to each ts file. - # Note that it will make NGINX edge cache always missed, so never enable HLS streaming if use NGINX edges. - # Overwrite by env SRS_VHOST_HLS_HLS_TS_CTX for all vhosts. - # Default: on - hls_ts_ctx on; - # the hls fragment in seconds, the duration of a piece of ts. # Overwrite by env SRS_VHOST_HLS_HLS_FRAGMENT for all vhosts. # default: 10 @@ -1776,15 +1755,15 @@ vhost hls.srs.com { # EXT-X-TARGETDURATION = hls_td_ratio * hls_fragment // init # EXT-X-TARGETDURATION = max(ts_duration, EXT-X-TARGETDURATION) // for each ts # Overwrite by env SRS_VHOST_HLS_HLS_TD_RATIO for all vhosts. - # default: 1.5 - hls_td_ratio 1.5; + # default: 1.0 + hls_td_ratio 1.0; # the audio overflow ratio. # for pure audio, the duration to reap the segment. - # for example, the hls_fragment is 10s, hls_aof_ratio is 2.0, - # the segment will reap to 20s for pure audio. + # for example, the hls_fragment is 10s, hls_aof_ratio is 1.2, + # the segment will reap to 12s for pure audio. # Overwrite by env SRS_VHOST_HLS_HLS_AOF_RATIO for all vhosts. - # default: 2.0 - hls_aof_ratio 2.0; + # default: 1.2 + hls_aof_ratio 1.2; # the hls window in seconds, the number of ts in m3u8. # Overwrite by env SRS_VHOST_HLS_HLS_WINDOW for all vhosts. # default: 60 @@ -1833,13 +1812,6 @@ vhost hls.srs.com { # Overwrite by env SRS_VHOST_HLS_HLS_TS_FILE for all vhosts. # default: [app]/[stream]-[seq].ts hls_ts_file [app]/[stream]-[seq].ts; - # whether use floor for the hls_ts_file path generation. - # if on, use floor(timestamp/hls_fragment) as the variable [timestamp], - # and use enhanced algorithm to calc deviation for segment. - # @remark when floor on, recommend the hls_segment>=2*gop. - # Overwrite by env SRS_VHOST_HLS_HLS_TS_FLOOR for all vhosts. - # default: off - hls_ts_floor off; # the hls entry prefix, which is base url of ts url. # for example, the prefix is: # http://your-server/ @@ -1875,20 +1847,48 @@ vhost hls.srs.com { # @remark 0 to disable dispose for publisher. # @remark apply for publisher timeout only, while "etc/init.d/srs stop" always dispose hls. # Overwrite by env SRS_VHOST_HLS_HLS_DISPOSE for all vhosts. - # default: 0 - hls_dispose 0; - # the max size to notify hls, - # to read max bytes from ts of specified cdn network, - # @remark only used when on_hls_notify is config. - # Overwrite by env SRS_VHOST_HLS_HLS_NB_NOTIFY for all vhosts. - # default: 64 - hls_nb_notify 64; + # default: 120 + hls_dispose 120; # whether wait keyframe to reap segment, # if off, reap segment when duration exceed the fragment, # if on, reap segment when duration exceed and got keyframe. # Overwrite by env SRS_VHOST_HLS_HLS_WAIT_KEYFRAME for all vhosts. # default: on hls_wait_keyframe on; + # whether use floor for the hls_ts_file path generation. + # if on, use floor(timestamp/hls_fragment) as the variable [timestamp], + # and use enhanced algorithm to calc deviation for segment. + # @remark when floor on, recommend the hls_segment>=2*gop. + # Overwrite by env SRS_VHOST_HLS_HLS_TS_FLOOR for all vhosts. + # default: off + hls_ts_floor off; + # the max size to notify hls, + # to read max bytes from ts of specified cdn network, + # @remark only used when on_hls_notify is config. + # Overwrite by env SRS_VHOST_HLS_HLS_NB_NOTIFY for all vhosts. + # default: 64 + hls_nb_notify 64; + + # Whether enable hls_ctx for HLS streaming, for which we create a "fake" connection for HTTP API and callback. + # For each HLS streaming session, we use a child m3u8 with a session identified by query "hls_ctx", it simply + # work as the session id. + # Once the HLS streaming session is created, we will cleanup it when timeout in 2*hls_window seconds. So it + # takes a long time period to identify the timeout. + # Now we got a HLS stremaing session, just like RTMP/WebRTC/HTTP-FLV streaming, we're able to stat the session + # as a "fake" connection, do HTTP callback when start playing the HLS streaming. You're able to do querying and + # authentication. + # Note that it will make NGINX edge cache always missed, so never enable HLS streaming if use NGINX edges. + # Overwrite by env SRS_VHOST_HLS_HLS_CTX for all vhosts. + # Default: on + hls_ctx on; + # For HLS pseudo streaming, whether enable the session for each TS segment. + # If enabled, SRS HTTP API will show the statistics about HLS streaming bandwidth, both m3u8 and ts file. Please + # note that it also consumes resource, because each ts file should be served by SRS, all NGINX cache will be + # missed because we add session id to each ts file. + # Note that it will make NGINX edge cache always missed, so never enable HLS streaming if use NGINX edges. + # Overwrite by env SRS_VHOST_HLS_HLS_TS_CTX for all vhosts. + # Default: on + hls_ts_ctx on; # whether using AES encryption. # Overwrite by env SRS_VHOST_HLS_HLS_KEYS for all vhosts. diff --git a/trunk/conf/hls.origin.conf b/trunk/conf/hls.origin.conf index aa7bc23b8d..fd4b95c9b7 100644 --- a/trunk/conf/hls.origin.conf +++ b/trunk/conf/hls.origin.conf @@ -12,6 +12,7 @@ vhost __defaultVhost__ { enabled on; # Note that it will make NGINX edge cache always missed, so never enable HLS streaming if use NGINX edges. hls_ctx off; + hls_ts_ctx off; } } diff --git a/trunk/conf/hls.realtime.conf b/trunk/conf/hls.realtime.conf index 11dac8e140..843284eb7c 100644 --- a/trunk/conf/hls.realtime.conf +++ b/trunk/conf/hls.realtime.conf @@ -14,8 +14,7 @@ http_server { vhost __defaultVhost__ { hls { enabled on; - hls_fragment 0.2; - hls_window 2; - hls_wait_keyframe off; + hls_fragment 2; + hls_window 10; } } diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 713b64c67e..fa35662a9f 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 5.0 Changelog +* v5.0, 2023-10-08, Merge [#3824](https://github.com/ossrs/srs/pull/3824): Solve the problem of inaccurate HLS TS duration. v5.0.187 (#3824) * v5.0, 2023-10-08, Merge [#3815](https://github.com/ossrs/srs/pull/3815): Use new cache image name. v5.0.186 (#3815) * v5.0, 2023-09-28, Merge [#3816](https://github.com/ossrs/srs/pull/3816): cherry-pick from develop, for srt utest. v5.0.185 (#3816) * v5.0, 2023-09-21, Merge [#3806](https://github.com/ossrs/srs/pull/3806): Build: Support sys-ssl for srt. v5.0.184 (#3806) diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index e0e9dd1c0f..21d759923f 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -6973,7 +6973,7 @@ double SrsConfig::get_hls_td_ratio(string vhost) { SRS_OVERWRITE_BY_ENV_FLOAT("srs.vhost.hls.hls_td_ratio"); // SRS_VHOST_HLS_HLS_TD_RATIO - static double DEFAULT = 1.5; + static double DEFAULT = 1.0; SrsConfDirective* conf = get_hls(vhost); if (!conf) { @@ -6992,7 +6992,7 @@ double SrsConfig::get_hls_aof_ratio(string vhost) { SRS_OVERWRITE_BY_ENV_FLOAT("srs.vhost.hls.hls_aof_ratio"); // SRS_VHOST_HLS_HLS_AOF_RATIO - static double DEFAULT = 2.0; + static double DEFAULT = 1.2; SrsConfDirective* conf = get_hls(vhost); if (!conf) { @@ -7183,7 +7183,7 @@ srs_utime_t SrsConfig::get_hls_dispose(string vhost) { SRS_OVERWRITE_BY_ENV_SECONDS("srs.vhost.hls.hls_dispose"); // SRS_VHOST_HLS_HLS_DISPOSE - static srs_utime_t DEFAULT = 0; + static srs_utime_t DEFAULT = 120 * SRS_UTIME_SECONDS; SrsConfDirective* conf = get_hls(vhost); if (!conf) { diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index 9c1c0fa0ef..8c35df12be 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -519,9 +519,12 @@ bool SrsHlsMuxer::is_segment_overflow() return false; } - // use N% deviation, to smoother. + // Use N% deviation, to smoother. srs_utime_t deviation = hls_ts_floor? SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0; - return current->duration() >= hls_fragment + deviation; + + // Keep in mind that we use max_td for the base duration, not the hls_fragment. To calculate + // max_td, multiply hls_fragment by hls_td_ratio. + return current->duration() >= max_td + deviation; } bool SrsHlsMuxer::wait_keyframe() @@ -564,8 +567,8 @@ srs_error_t SrsHlsMuxer::flush_audio(SrsTsMessageCache* cache) } // update the duration of segment. - current->append(cache->audio->pts / 90); - + update_duration(cache->audio->dts); + if ((err = current->tscw->write_audio(cache->audio)) != srs_success) { return srs_error_wrap(err, "hls: write audio"); } @@ -593,8 +596,8 @@ srs_error_t SrsHlsMuxer::flush_video(SrsTsMessageCache* cache) srs_assert(current); // update the duration of segment. - current->append(cache->video->dts / 90); - + update_duration(cache->video->dts); + if ((err = current->tscw->write_video(cache->video)) != srs_success) { return srs_error_wrap(err, "hls: write video"); } @@ -605,6 +608,11 @@ srs_error_t SrsHlsMuxer::flush_video(SrsTsMessageCache* cache) return err; } +void SrsHlsMuxer::update_duration(uint64_t dts) +{ + current->append(dts / 90); +} + srs_error_t SrsHlsMuxer::segment_close() { srs_error_t err = do_segment_close(); @@ -635,9 +643,9 @@ srs_error_t SrsHlsMuxer::do_segment_close() // valid, add to segments if segment duration is ok // when too small, it maybe not enough data to play. // when too large, it maybe timestamp corrupt. - // make the segment more acceptable, when in [min, max_td * 2], it's ok. + // make the segment more acceptable, when in [min, max_td * 3], it's ok. bool matchMinDuration = current->duration() >= SRS_HLS_SEGMENT_MIN_DURATION; - bool matchMaxDuration = current->duration() <= max_td * 2 * 1000; + bool matchMaxDuration = current->duration() <= max_td * 3 * 1000; if (matchMinDuration && matchMaxDuration) { // rename from tmp to real path if ((err = current->rename()) != srs_success) { @@ -899,8 +907,9 @@ srs_error_t SrsHlsController::on_publish(SrsRequest* req) std::string vhost = req->vhost; 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); // get the hls m3u8 ts list entry prefix config @@ -944,9 +953,9 @@ srs_error_t SrsHlsController::on_publish(SrsRequest* req) // This config item is used in SrsHls, we just log its value here. bool hls_dts_directly = _srs_config->get_vhost_hls_dts_directly(req->vhost); - srs_trace("hls: win=%dms, frag=%dms, prefix=%s, path=%s, m3u8=%s, ts=%s, aof=%.2f, floor=%d, clean=%d, waitk=%d, dispose=%dms, dts_directly=%d", + srs_trace("hls: win=%dms, frag=%dms, prefix=%s, path=%s, m3u8=%s, ts=%s, tdr=%.2f, aof=%.2f, floor=%d, clean=%d, waitk=%d, dispose=%dms, dts_directly=%d", srsu2msi(hls_window), srsu2msi(hls_fragment), entry_prefix.c_str(), path.c_str(), m3u8_file.c_str(), ts_file.c_str(), - hls_aof_ratio, ts_floor, cleanup, wait_keyframe, srsu2msi(hls_dispose), hls_dts_directly); + hls_td_ratio, hls_aof_ratio, ts_floor, cleanup, wait_keyframe, srsu2msi(hls_dispose), hls_dts_directly); return err; } @@ -996,6 +1005,10 @@ srs_error_t SrsHlsController::write_audio(SrsAudioFrame* frame, int64_t pts) if ((err = tsmc->cache_audio(frame, pts)) != srs_success) { return srs_error_wrap(err, "hls: cache audio"); } + + // First, update the duration of the segment, as we might reap the segment. The duration should + // cover from the first frame to the last frame. + muxer->update_duration(tsmc->audio->dts); // reap when current source is pure audio. // it maybe changed when stream info changed, @@ -1038,6 +1051,10 @@ srs_error_t SrsHlsController::write_video(SrsVideoFrame* frame, int64_t dts) if ((err = tsmc->cache_video(frame, dts)) != srs_success) { return srs_error_wrap(err, "hls: cache video"); } + + // First, update the duration of the segment, as we might reap the segment. The duration should + // cover from the first frame to the last frame. + muxer->update_duration(tsmc->video->dts); // when segment overflow, reap if possible. if (muxer->is_segment_overflow()) { diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index 7e2aa26728..c3fa7ab47e 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -200,6 +200,10 @@ class SrsHlsMuxer virtual bool pure_audio(); virtual srs_error_t flush_audio(SrsTsMessageCache* cache); virtual srs_error_t flush_video(SrsTsMessageCache* cache); + // When flushing video or audio, we update the duration. But, we should also update the + // duration before closing the segment. Keep in mind that it's fine to update the duration + // several times using the same dts timestamp. + void update_duration(uint64_t dts); // Close segment(ts). virtual srs_error_t segment_close(); private: diff --git a/trunk/src/core/srs_core_version5.hpp b/trunk/src/core/srs_core_version5.hpp index dc7803eac3..6f5942ec2f 100644 --- a/trunk/src/core/srs_core_version5.hpp +++ b/trunk/src/core/srs_core_version5.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 5 #define VERSION_MINOR 0 -#define VERSION_REVISION 186 +#define VERSION_REVISION 187 #endif diff --git a/trunk/src/utest/srs_utest_config.cpp b/trunk/src/utest/srs_utest_config.cpp index 977210ebcb..1c1f4fa188 100644 --- a/trunk/src/utest/srs_utest_config.cpp +++ b/trunk/src/utest/srs_utest_config.cpp @@ -2085,7 +2085,7 @@ VOID TEST(ConfigUnitTest, CheckDefaultValuesVhost) if (true) { HELPER_ASSERT_SUCCESS(conf.parse(_MIN_OK_CONF)); - EXPECT_EQ(0, (int)conf.get_hls_dispose("")); + EXPECT_EQ(120 * SRS_UTIME_SECONDS, (int)conf.get_hls_dispose("")); EXPECT_EQ(10 * SRS_UTIME_SECONDS, conf.get_hls_fragment("")); EXPECT_EQ(60 * SRS_UTIME_SECONDS, conf.get_hls_window(""));