Skip to content

Commit

Permalink
Use segment duration in buffer depth calculation
Browse files Browse the repository at this point in the history
Segment start,end time was used previously, which could result in
problems if there are discontinuity in the streams. E.g. if the
stream has timestamp, 10000, 10001, 10002 and then next segment
comes in with timestamp 1. With the previous logic, all the segments
would remain in the time shift buffer until after 10000 segments
even with a small time shift buffer depth of 10.

This could also happen when timestamp wraps around, which could
happen during long time of live streaming.

This change will also be useful to support multi-period live DASH.

Fixes shaka-project#563.

Change-Id: Ie078d76c6e4af13ade9ad46191c8e3529069ed4d
  • Loading branch information
kqyang committed Apr 17, 2019
1 parent fa2c440 commit b85e5c9
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 150 deletions.
59 changes: 33 additions & 26 deletions packager/hls/base/media_playlist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -305,19 +305,6 @@ std::string PlacementOpportunityEntry::ToString() {
return "#EXT-X-PLACEMENT-OPPORTUNITY";
}

double LatestSegmentStartTime(
const std::list<std::unique_ptr<HlsEntry>>& entries) {
DCHECK(!entries.empty());
for (auto iter = entries.rbegin(); iter != entries.rend(); ++iter) {
if (iter->get()->type() == HlsEntry::EntryType::kExtInf) {
const SegmentInfoEntry* segment_info =
reinterpret_cast<SegmentInfoEntry*>(iter->get());
return segment_info->start_time();
}
}
return 0.0;
}

} // namespace

HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {}
Expand Down Expand Up @@ -530,19 +517,39 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
return;
}

// In order for the oldest segment to be accessible for at least
// |time_shift_buffer_depth| seconds, the latest segment should not be in the
// sliding window since the player could be playing any part of the latest
// segment. So the current segment duration is added to the sum of segment
// durations (in the manifest/playlist) after sliding the window.
SlideWindow();

const double start_time_seconds =
static_cast<double>(start_time) / time_scale_;
const double segment_duration_seconds =
static_cast<double>(duration) / time_scale_;
longest_segment_duration_ =
std::max(longest_segment_duration_, segment_duration_seconds);
bandwidth_estimator_.AddBlock(size, segment_duration_seconds);
current_buffer_depth_ += segment_duration_seconds;

if (!entries_.empty() &&
entries_.back()->type() == HlsEntry::EntryType::kExtInf) {
const SegmentInfoEntry* segment_info =
static_cast<SegmentInfoEntry*>(entries_.back().get());
if (segment_info->start_time() > start_time_seconds) {
LOG(WARNING)
<< "Insert a discontinuity tag after the segment with start time "
<< segment_info->start_time() << " as the next segment starts at "
<< start_time_seconds << ".";
entries_.emplace_back(new DiscontinuityEntry());
}
}

entries_.emplace_back(new SegmentInfoEntry(
segment_file_name, start_time_seconds, segment_duration_seconds,
use_byte_range_, start_byte_offset, size, previous_segment_end_offset_));
previous_segment_end_offset_ = start_byte_offset + size - 1;
SlideWindow();
}

void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) {
Expand All @@ -559,7 +566,9 @@ void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) {

const double segment_duration_seconds =
next_timestamp_seconds - segment_info->start_time();
segment_info->set_duration(segment_duration_seconds);
// It could be negative if timestamp messed up.
if (segment_duration_seconds > 0)
segment_info->set_duration(segment_duration_seconds);
longest_segment_duration_ =
std::max(longest_segment_duration_, segment_duration_seconds);
break;
Expand All @@ -568,22 +577,15 @@ void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) {
}

void MediaPlaylist::SlideWindow() {
DCHECK(!entries_.empty());
if (hls_params_.time_shift_buffer_depth <= 0.0 ||
hls_params_.playlist_type != HlsPlaylistType::kLive) {
return;
}
DCHECK_GT(time_scale_, 0u);

// The start time of the latest segment is considered the current_play_time,
// and this should guarantee that the latest segment will stay in the list.
const double current_play_time = LatestSegmentStartTime(entries_);
if (current_play_time <= hls_params_.time_shift_buffer_depth)
if (current_buffer_depth_ <= hls_params_.time_shift_buffer_depth)
return;

const double timeshift_limit =
current_play_time - hls_params_.time_shift_buffer_depth;

// Temporary list to hold the EXT-X-KEYs. For example, this allows us to
// remove <3> without removing <1> and <2> below (<1> and <2> are moved to the
// temporary list and added back later).
Expand All @@ -607,12 +609,17 @@ void MediaPlaylist::SlideWindow() {
++discontinuity_sequence_number_;
} else {
DCHECK_EQ(entry_type, HlsEntry::EntryType::kExtInf);

const SegmentInfoEntry& segment_info =
*reinterpret_cast<SegmentInfoEntry*>(last->get());
const double last_segment_end_time =
segment_info.start_time() + segment_info.duration();
if (timeshift_limit < last_segment_end_time)
// Remove the current segment only if it falls completely out of time
// shift buffer range.
const bool segment_within_time_shift_buffer =
current_buffer_depth_ - segment_info.duration() <
hls_params_.time_shift_buffer_depth;
if (segment_within_time_shift_buffer)
break;
current_buffer_depth_ -= segment_info.duration();
RemoveOldSegment(segment_info.start_time());
media_sequence_number_++;
}
Expand Down
3 changes: 3 additions & 0 deletions packager/hls/base/media_playlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,10 @@ class MediaPlaylist {
bool target_duration_set_ = false;
uint32_t target_duration_ = 0;

// TODO(kqyang): This could be managed better by a separate class, than having
// all them managed in MediaPlaylist.
std::list<std::unique_ptr<HlsEntry>> entries_;
double current_buffer_depth_ = 0;
// A list to hold the file names of the segments to be removed temporarily.
// Once a file is actually removed, it is removed from the list.
std::list<std::string> segments_to_be_removed_;
Expand Down
97 changes: 27 additions & 70 deletions packager/mpd/base/representation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,33 +79,6 @@ uint32_t GetTimeScale(const MediaInfo& media_info) {
return 1;
}

int64_t LastSegmentStartTime(const SegmentInfo& segment_info) {
return segment_info.start_time + segment_info.duration * segment_info.repeat;
}

// This is equal to |segment_info| end time
int64_t LastSegmentEndTime(const SegmentInfo& segment_info) {
return segment_info.start_time +
segment_info.duration * (segment_info.repeat + 1);
}

int64_t LatestSegmentStartTime(const std::list<SegmentInfo>& segments) {
DCHECK(!segments.empty());
const SegmentInfo& latest_segment = segments.back();
return LastSegmentStartTime(latest_segment);
}

// Given |timeshift_limit|, finds out the number of segments that are no longer
// valid and should be removed from |segment_info|.
uint64_t SearchTimedOutRepeatIndex(int64_t timeshift_limit,
const SegmentInfo& segment_info) {
DCHECK_LE(timeshift_limit, LastSegmentEndTime(segment_info));
if (timeshift_limit < segment_info.start_time)
return 0;

return (timeshift_limit - segment_info.start_time) / segment_info.duration;
}

} // namespace

Representation::Representation(
Expand Down Expand Up @@ -203,16 +176,21 @@ void Representation::AddNewSegment(int64_t start_time,
return;
}

// In order for the oldest segment to be accessible for at least
// |time_shift_buffer_depth| seconds, the latest segment should not be in the
// sliding window since the player could be playing any part of the latest
// segment. So the current segment duration is added to the sum of segment
// durations (in the manifest/playlist) after sliding the window.
SlideWindow();

if (state_change_listener_)
state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);

AddSegmentInfo(start_time, duration);
current_buffer_depth_ += segment_infos_.back().duration;

bandwidth_estimator_.AddBlock(
size, static_cast<double>(duration) / media_info_.reference_time_scale());

SlideWindow();
DCHECK_GE(segment_infos_.size(), 1u);
}

void Representation::SetSampleDuration(uint32_t frame_duration) {
Expand Down Expand Up @@ -435,68 +413,47 @@ int64_t Representation::AdjustDuration(int64_t duration) const {
}

void Representation::SlideWindow() {
DCHECK(!segment_infos_.empty());
if (mpd_options_.mpd_params.time_shift_buffer_depth <= 0.0 ||
mpd_options_.mpd_type == MpdType::kStatic)
return;

const uint32_t time_scale = GetTimeScale(media_info_);
DCHECK_GT(time_scale, 0u);

int64_t time_shift_buffer_depth = static_cast<int64_t>(
const int64_t time_shift_buffer_depth = static_cast<int64_t>(
mpd_options_.mpd_params.time_shift_buffer_depth * time_scale);

// The start time of the latest segment is considered the current_play_time,
// and this should guarantee that the latest segment will stay in the list.
const int64_t current_play_time = LatestSegmentStartTime(segment_infos_);
if (current_play_time <= time_shift_buffer_depth)
if (current_buffer_depth_ <= time_shift_buffer_depth)
return;

const int64_t timeshift_limit = current_play_time - time_shift_buffer_depth;

// First remove all the SegmentInfos that are completely out of range, by
// looking at the very last segment's end time.
std::list<SegmentInfo>::iterator first = segment_infos_.begin();
std::list<SegmentInfo>::iterator last = first;
for (; last != segment_infos_.end(); ++last) {
const int64_t last_segment_end_time = LastSegmentEndTime(*last);
if (timeshift_limit < last_segment_end_time)
// Remove the current segment only if it falls completely out of time shift
// buffer range.
while (last->repeat >= 0 &&
current_buffer_depth_ - last->duration >= time_shift_buffer_depth) {
current_buffer_depth_ -= last->duration;
RemoveOldSegment(&*last);
start_number_++;
}
if (last->repeat >= 0)
break;
RemoveSegments(last->start_time, last->duration, last->repeat + 1);
start_number_ += last->repeat + 1;
}
segment_infos_.erase(first, last);

// Now some segment in the first SegmentInfo should be left in the list.
SegmentInfo* first_segment_info = &segment_infos_.front();
DCHECK_LE(timeshift_limit, LastSegmentEndTime(*first_segment_info));

// Identify which segments should still be in the SegmentInfo.
const uint64_t repeat_index =
SearchTimedOutRepeatIndex(timeshift_limit, *first_segment_info);
if (repeat_index == 0)
return;

RemoveSegments(first_segment_info->start_time, first_segment_info->duration,
repeat_index);

first_segment_info->start_time = first_segment_info->start_time +
first_segment_info->duration * repeat_index;
first_segment_info->repeat = first_segment_info->repeat - repeat_index;
start_number_ += repeat_index;
}

void Representation::RemoveSegments(int64_t start_time,
int64_t duration,
uint64_t num_segments) {
void Representation::RemoveOldSegment(SegmentInfo* segment_info) {
int64_t segment_start_time = segment_info->start_time;
segment_info->start_time += segment_info->duration;
segment_info->repeat--;

if (mpd_options_.mpd_params.preserved_segments_outside_live_window == 0)
return;

for (size_t i = 0; i < num_segments; ++i) {
segments_to_be_removed_.push_back(media::GetSegmentName(
media_info_.segment_template(), start_time + i * duration,
start_number_ - 1 + i, media_info_.bandwidth()));
}
segments_to_be_removed_.push_back(
media::GetSegmentName(media_info_.segment_template(), segment_start_time,
start_number_ - 1, media_info_.bandwidth()));
while (segments_to_be_removed_.size() >
mpd_options_.mpd_params.preserved_segments_outside_live_window) {
VLOG(2) << "Deleting " << segments_to_be_removed_.front();
Expand Down
8 changes: 4 additions & 4 deletions packager/mpd/base/representation.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,8 @@ class Representation {
// |start_number_| by the number of segments removed.
void SlideWindow();

// Remove |num_segments| starting from |start_time| with |duration|.
void RemoveSegments(int64_t start_time,
int64_t duration,
uint64_t num_segments);
// Remove the first segment in |segment_info|.
void RemoveOldSegment(SegmentInfo* segment_info);

// Note: Because 'mimeType' is a required field for a valid MPD, these return
// strings.
Expand All @@ -212,6 +210,8 @@ class Representation {
// any logic using this can assume only one set.
MediaInfo media_info_;
std::list<ContentProtectionElement> content_protection_elements_;

int64_t current_buffer_depth_ = 0;
// TODO(kqyang): Address sliding window issue with multiple periods.
std::list<SegmentInfo> segment_infos_;
// A list to hold the file names of the segments to be removed temporarily.
Expand Down
Loading

0 comments on commit b85e5c9

Please sign in to comment.