1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

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>
This commit is contained in:
Winlin 2023-10-09 19:22:41 +08:00 committed by GitHub
parent d10e16e335
commit a1e4f61dd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 63 deletions

View file

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

View file

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

View file

@ -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:

View file

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

View file

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

View file

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