Skip to content

Commit

Permalink
IPTV HLS fixes (#1000)
Browse files Browse the repository at this point in the history
Slightly longer distance to live to accommodate varying IPTV throughput.
Improved log messages and improved comment in the code.

Refs #936
  • Loading branch information
kmdewaal authored Dec 20, 2024
1 parent 3b14fdf commit 3a9ca2d
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 38 deletions.
2 changes: 1 addition & 1 deletion mythtv/libs/libmythtv/HLS/m3u.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ namespace M3U
* file; it MUST NOT occur more than once. Its format is:
*/
is_vod = true;
LOG(VB_RECORD, LOG_INFO, loc + "video on demand (vod) mode");
LOG(VB_RECORD, LOG_INFO, loc + " video on demand (vod) mode");
return true;
}

Expand Down
85 changes: 52 additions & 33 deletions mythtv/libs/libmythtv/recorders/HLS/HLSReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
#include "HLSReader.h"
#include "HLS/m3u.h"

#define LOC QString("HLSReader[%1]%2: ")\
.arg(m_inputId).arg(m_curstream ? "(" + m_curstream->M3U8Url() + ")" : "")
#define LOC QString("HLSReader[%1]: ").arg(m_inputId)

/**
* Handles relative URLs without breaking URI encoded parameters by avoiding
Expand Down Expand Up @@ -326,7 +325,7 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
return false;
}

/* What is the version ? */
// Version is 1 if not specified, otherwise must be in range 1 to 7.
int version = 1;
int p = buffer.indexOf("#EXT-X-VERSION:");
if (p >= 0)
Expand All @@ -339,7 +338,7 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
if (buffer.indexOf("#EXT-X-STREAM-INF") >= 0)
{
// Meta index file
LOG(VB_RECORD, LOG_INFO, LOC + "Meta index file");
LOG(VB_RECORD, LOG_DEBUG, LOC + "Master Playlist");

/* M3U8 Meta Index file */
text.seek(0); // rewind
Expand Down Expand Up @@ -392,7 +391,7 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
}
else
{
LOG(VB_RECORD, LOG_DEBUG, LOC + "Meta playlist");
LOG(VB_RECORD, LOG_DEBUG, LOC + "Media Playlist");

HLSRecStream *hls = stream;
if (stream == nullptr)
Expand Down Expand Up @@ -432,20 +431,20 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
/* Store version */
hls->SetVersion(version);
}
LOG(VB_RECORD, LOG_DEBUG, LOC +
QString("%1 Playlist HLS protocol version: %2")
LOG(VB_RECORD, LOG_INFO, LOC +
QString("%1 Media Playlist HLS protocol version: %2")
.arg(hls->Live() ? "Live": "VOD").arg(version));

// rewind
text.seek(0);

QString title;
std::chrono::seconds segment_duration = -1s;
int64_t first_sequence = -1;
int64_t sequence_num = 0;
int skipped = 0;
QString title; // From playlist, #EXTINF:<duration>,<title>
std::chrono::seconds segment_duration = -1s; // From playlist, e.g. #EXTINF:10.24,
int64_t first_sequence = -1; // Sequence number of first segment to be recorded
int64_t sequence_num = 0; // Sequence number of next segment to be read
int skipped = 0; // Segments skipped, sequence number at or below current

SegmentContainer new_segments;
SegmentContainer new_segments; // All segments read from Media Playlist

QMutexLocker lock(&m_seqLock);
while (!m_cancel)
Expand Down Expand Up @@ -480,6 +479,10 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
if (first_sequence < 0)
first_sequence = sequence_num;
}
else if (line.startsWith(QLatin1String("#EXT-X-MEDIA")))
{
// Not handled yet
}
else if (line.startsWith(QLatin1String("#EXT-X-KEY")))
{
#ifdef USING_LIBCRYPTO
Expand All @@ -506,6 +509,7 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
QDateTime date;
if (!M3U::ParseProgramDateTime(line, StreamURL(), date))
return false;
// Not handled yet
}
else if (line.startsWith(QLatin1String("#EXT-X-ALLOW-CACHE")))
{
Expand All @@ -519,7 +523,7 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
int sequence = 0;
if (!M3U::ParseDiscontinuitySequence(line, StreamURL(), sequence))
return false;
SetDiscontinuitySequence(sequence);
hls->SetDiscontinuitySequence(sequence);
}
else if (line.startsWith(QLatin1String("#EXT-X-DISCONTINUITY")))
{
Expand Down Expand Up @@ -566,6 +570,12 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
}
}

LOG(VB_RECORD, LOG_DEBUG, LOC +
QString(" first_sequence:%1").arg(first_sequence) +
QString(" sequence_num:%1").arg(sequence_num) +
QString(" m_curSeq:%1").arg(m_curSeq) +
QString(" skipped:%1").arg(skipped));

if (sequence_num < m_curSeq)
{
// Sequence has been reset
Expand All @@ -576,26 +586,30 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
return false;
}

// For near-live skip all segments that are in the past.
// For near-live skip all segments that are too far in the past.
if (m_curSeq < 0)
{
// Compute number of segments for 20 seconds buffer from live.
// If the duration is not know keep 4 segments.
int numseg = 4;
// Compute number of segments for 30 seconds buffer from live.
// If the duration is not know keep 3 segments.
int numseg = std::min(3, new_segments.size());
if (hls->TargetDuration() > 0s)
{
numseg = 20s / hls->TargetDuration();
numseg = std::clamp(numseg, 2, 20);
numseg = 30s / hls->TargetDuration();
numseg = std::clamp(numseg, 2, 10);
}

// Trim new_segments to leave only the last part
if (new_segments.size() > numseg)
{
int size_before = new_segments.size();
SegmentContainer::iterator Iseg = new_segments.begin() + (new_segments.size() - numseg);
new_segments.erase(new_segments.begin(), Iseg);
SegmentContainer::iterator it = new_segments.begin() + (new_segments.size() - numseg);
new_segments.erase(new_segments.begin(), it);
LOG(VB_RECORD, LOG_INFO, LOC +
QString(" Read last %1 segments instead of %2 for near-live")
QString("Read last %1 segments instead of %2 for near-live")
.arg(new_segments.size()).arg(size_before));

// Adjust first_sequence to first segment to be read
first_sequence += size_before - new_segments.size();
}
}

Expand Down Expand Up @@ -652,6 +666,13 @@ bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
m_playlistSize = new_segments.size() + skipped;
int behind = m_segments.size() - m_playlistSize;
int max_behind = m_playlistSize / 2;

LOG(VB_RECORD, LOG_INFO, LOC +
QString("new_segments.size():%1 ").arg(new_segments.size()) +
QString("m_playlistSize:%1 ").arg(m_playlistSize) +
QString("behind:%1 ").arg(behind) +
QString("max_behind:%1").arg(max_behind));

if (behind > max_behind)
{
LOG(VB_RECORD, LOG_WARNING, LOC +
Expand Down Expand Up @@ -948,19 +969,19 @@ int HLSReader::DownloadSegmentData(MythSingleDownload& downloader,
uint64_t bandwidth = hls->AverageBandwidth();

LOG(VB_RECORD, LOG_DEBUG, LOC +
QString("Downloading %1 bandwidth %2 bitrate %3")
QString("Downloading seq#%1 av.bandwidth:%2 bitrate %3")
.arg(segment.Sequence()).arg(bandwidth).arg(hls->Bitrate()));

/* sanity check - can we download this segment on time? */
if ((bandwidth > 0) && (hls->Bitrate() > 0))
if ((bandwidth > 0) && (hls->Bitrate() > 0) && (segment.Duration().count() > 0))
{
uint64_t size = (segment.Duration().count() * hls->Bitrate()); /* bits */
auto estimated_time = std::chrono::seconds(size / bandwidth);
if (estimated_time > segment.Duration())
{
LOG(VB_RECORD, LOG_WARNING, LOC +
QString("downloading of %1 will take %2s, "
"which is longer than its playback (%3s) at %4kiB/s")
"which is longer than its playback (%3s) at %4KiB/s")
.arg(segment.Sequence())
.arg(estimated_time.count())
.arg(segment.Duration().count())
Expand Down Expand Up @@ -1055,19 +1076,17 @@ int HLSReader::DownloadSegmentData(MythSingleDownload& downloader,
downloadduration = 1ms;

/* bits/sec */
bandwidth = 8 * 1000ULL * segment_len / downloadduration.count();
bandwidth = 8ULL * 1000 * segment_len / downloadduration.count();
hls->AverageBandwidth(bandwidth);
hls->SetCurrentByteRate(static_cast<uint64_t>
((static_cast<double>(segment_len) /
static_cast<double>(segment.Duration().count()))));
hls->SetCurrentByteRate(segment_len/segment.Duration().count());

LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
QString("%1 took %3ms for %4 bytes: "
"bandwidth:%5kiB/s")
QString("%1 took %2ms for %3 bytes: bandwidth:%4KiB/s byterate:%5KiB/s")
.arg(segment.Sequence())
.arg(downloadduration.count())
.arg(segment_len)
.arg(bandwidth / 8192.0));
.arg(bandwidth / 8192.0)
.arg(hls->CurrentByteRate() / 1024.0));

return m_slowCnt;
}
Expand Down
10 changes: 6 additions & 4 deletions mythtv/libs/libmythtv/recorders/HLS/HLSReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class MTV_PUBLIC HLSReader
void ResetStream(void)
{ QMutexLocker lock(&m_streamLock); m_curstream = nullptr; }
void ResetSequence(void) { m_curSeq = -1; }
void SetDiscontinuitySequence(int s) { m_sequence = s; }


QString StreamURL(void) const
{ return QString("%1").arg(m_curstream ? m_curstream->M3U8Url() : ""); }
Expand Down Expand Up @@ -88,7 +88,6 @@ class MTV_PUBLIC HLSReader
void IncreaseBitrate(int progid);

// Downloading
bool LoadSegments(HLSRecStream & hlsstream);
int DownloadSegmentData(MythSingleDownload& downloader, HLSRecStream* hls,
const HLSRecSegment& segment, int playlist_size);

Expand Down Expand Up @@ -116,11 +115,16 @@ class MTV_PUBLIC HLSReader
int m_playlistSize {0};
bool m_bandwidthCheck {false};
uint m_prebufferCnt {10};

QMutex m_seqLock;

mutable QMutex m_streamLock;

mutable QMutex m_workerLock;

QMutex m_throttleLock;
QWaitCondition m_throttleCond;

bool m_debug {false};
int m_debugCnt {0};

Expand All @@ -129,8 +133,6 @@ class MTV_PUBLIC HLSReader
QByteArray m_buffer;
QMutex m_bufLock;

int m_sequence {0}; // Discontinuity sequence number

// Log message
int m_inputId {0};
};
Expand Down
3 changes: 3 additions & 0 deletions mythtv/libs/libmythtv/recorders/HLS/HLSStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class HLSRecStream
void SetVersion(int x) { m_version = x; }
std::chrono::seconds TargetDuration(void) const { return m_targetDuration; }
void SetTargetDuration(std::chrono::seconds x) { m_targetDuration = x; }
int DiscontinuitySequence(void) { return m_discontSeq; }
void SetDiscontinuitySequence(int s) { m_discontSeq = s; }
uint64_t AverageBandwidth(void) const { return static_cast<uint64_t>(m_bandwidth); }
uint64_t Bitrate(void) const { return m_bitrate; }
void SetBitrate(uint64_t bitrate) { m_bitrate = bitrate; }
Expand Down Expand Up @@ -82,6 +84,7 @@ class HLSRecStream
uint64_t m_curByteRate {0};
uint64_t m_bitrate; // bitrate of stream content (bits per second)
std::chrono::seconds m_duration {0s}; // duration of the stream
int m_discontSeq {0}; // Discontinuity sequence number
bool m_live {true};
int64_t m_bandwidth {0}; // measured average download bandwidth (bits/second)
double m_sumBandwidth {0.0};
Expand Down

0 comments on commit 3a9ca2d

Please sign in to comment.