Solve the problem of inaccurate HLS TS duration. v5.0.187 v6.0.87 (#3824)

1. The comment on the ratio configuration says it can affect the slice
duration, but there is no effect after configuring it.
2. The default hls_td_ratio is 1.5, and after setting it to 1, the
duration is still slightly more than 10 seconds.
3. Even if the GOP is an integer, like 1 second, the slice is still a
non-integer, like 0.998 seconds, which seems a bit unreliable.
4. In the duration of the TS in the m3u8 file, it is one frame less than
the duration of the slice.
5. Set hls_dispose to 120s to dispose HLS files when no stream.
6. Use docker.conf for docker.

Before this patch:

```
#EXTINF:10.983, no desc
livestream-0.ts?hls_ctx=3p095hq0
```

After this patch:

```
#EXTINF:10.000, no desc
livestream-0.ts?hls_ctx=3p095hq0
```

Note: If the fragment is set to 10 seconds, but the GOP size cannot be
divided by 10, such as not 1, 2, 5, or 10, then the duration of ts will
still be more than 10 seconds.


---------

Co-authored-by: john <hondaxiao@tencent.com>
pull/3825/head
Winlin 1 year ago committed by GitHub
parent d10e16e335
commit a1e4f61dd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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"]

@ -1751,27 +1751,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
@ -1780,15 +1759,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
@ -1837,13 +1816,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/
@ -1879,20 +1851,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.

@ -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;
}
}

@ -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;
}
}

@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v6-changes"></a>
## SRS 6.0 Changelog
* v6.0, 2023-10-08, Merge [#3824](https://github.com/ossrs/srs/pull/3824): Solve the problem of inaccurate HLS TS duration. v6.0.87 (#3824)
* v6.0, 2023-10-08, Merge [#3815](https://github.com/ossrs/srs/pull/3815): Use new cache image name. v6.0.86 (#3815)
* v6.0, 2023-09-26, Merge [#3811](https://github.com/ossrs/srs/pull/3811): Turn off the related utests H265 option. v6.0.85 (#3811)
* v6.0, 2023-09-25, Merge [#3810](https://github.com/ossrs/srs/pull/3810): Change dev code for John. v6.0.84 (#3810)
@ -77,7 +78,7 @@ The changelog for SRS.
* v6.0, 2023-01-19, Merge [#3318](https://github.com/ossrs/srs/pull/3318): RTC: fix rtc publisher pli cid. v6.0.19 (#3318)
* v6.0, 2023-01-18, Merge [#3382](https://github.com/ossrs/srs/pull/3382): Rewrite research/api-server code by Go, remove Python. v6.0.18 (#3382)
* v6.0, 2023-01-18, Merge [#3386](https://github.com/ossrs/srs/pull/3386): SRT: fix crash when srt_to_rtmp off. v6.0.17 (#3386)
* v5.0, 2023-01-17, Merge [#3385](https://github.com/ossrs/srs/pull/3385): API: Support server/pid/service label for exporter and api. v6.0.16 (#3385)
* v6.0, 2023-01-17, Merge [#3385](https://github.com/ossrs/srs/pull/3385): API: Support server/pid/service label for exporter and api. v6.0.16 (#3385)
* v6.0, 2023-01-17, Merge [#3379](https://github.com/ossrs/srs/pull/3379): H265: Support demux vps/pps info. v6.0.15
* v6.0, 2023-01-08, Merge [#3360](https://github.com/ossrs/srs/pull/3360): H265: Support DVR HEVC stream to MP4. v6.0.14
* v6.0, 2023-01-06, Merge [#3363](https://github.com/ossrs/srs/issues/3363): HTTP: Add CORS Header for private network access. v6.0.13
@ -98,6 +99,8 @@ The changelog for SRS.
<a name="v5-changes"></a>
## 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)
* v5.0, 2023-09-21, Merge [#3808](https://github.com/ossrs/srs/pull/3808): Upgrade libsrt to v1.5.3. v5.0.183 (#3808)

@ -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) {

@ -542,9 +542,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()
@ -586,8 +589,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");
}
@ -615,7 +618,7 @@ 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");
@ -627,6 +630,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();
@ -657,9 +665,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) {
@ -920,8 +928,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
@ -965,9 +974,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;
}
@ -1017,6 +1026,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,
@ -1064,6 +1077,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()) {

@ -204,6 +204,10 @@ public:
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:

@ -9,6 +9,6 @@
#define VERSION_MAJOR 5
#define VERSION_MINOR 0
#define VERSION_REVISION 184
#define VERSION_REVISION 187
#endif

@ -9,6 +9,6 @@
#define VERSION_MAJOR 6
#define VERSION_MINOR 0
#define VERSION_REVISION 86
#define VERSION_REVISION 87
#endif

@ -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(""));

Loading…
Cancel
Save