Use segment duration in buffer depth calculation

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 #563.

Change-Id: Ie078d76c6e4af13ade9ad46191c8e3529069ed4d
This commit is contained in:
KongQun Yang 2019-03-21 17:12:30 -07:00
parent fa2c4409a6
commit b85e5c9368
6 changed files with 118 additions and 150 deletions

View File

@ -305,19 +305,6 @@ std::string PlacementOpportunityEntry::ToString() {
return "#EXT-X-PLACEMENT-OPPORTUNITY"; 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 } // namespace
HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {} HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {}
@ -530,6 +517,13 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
return; 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 = const double start_time_seconds =
static_cast<double>(start_time) / time_scale_; static_cast<double>(start_time) / time_scale_;
const double segment_duration_seconds = const double segment_duration_seconds =
@ -537,12 +531,25 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
longest_segment_duration_ = longest_segment_duration_ =
std::max(longest_segment_duration_, segment_duration_seconds); std::max(longest_segment_duration_, segment_duration_seconds);
bandwidth_estimator_.AddBlock(size, 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( entries_.emplace_back(new SegmentInfoEntry(
segment_file_name, start_time_seconds, segment_duration_seconds, segment_file_name, start_time_seconds, segment_duration_seconds,
use_byte_range_, start_byte_offset, size, previous_segment_end_offset_)); use_byte_range_, start_byte_offset, size, previous_segment_end_offset_));
previous_segment_end_offset_ = start_byte_offset + size - 1; previous_segment_end_offset_ = start_byte_offset + size - 1;
SlideWindow();
} }
void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) { void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) {
@ -559,6 +566,8 @@ void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) {
const double segment_duration_seconds = const double segment_duration_seconds =
next_timestamp_seconds - segment_info->start_time(); next_timestamp_seconds - segment_info->start_time();
// It could be negative if timestamp messed up.
if (segment_duration_seconds > 0)
segment_info->set_duration(segment_duration_seconds); segment_info->set_duration(segment_duration_seconds);
longest_segment_duration_ = longest_segment_duration_ =
std::max(longest_segment_duration_, segment_duration_seconds); std::max(longest_segment_duration_, segment_duration_seconds);
@ -568,22 +577,15 @@ void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) {
} }
void MediaPlaylist::SlideWindow() { void MediaPlaylist::SlideWindow() {
DCHECK(!entries_.empty());
if (hls_params_.time_shift_buffer_depth <= 0.0 || if (hls_params_.time_shift_buffer_depth <= 0.0 ||
hls_params_.playlist_type != HlsPlaylistType::kLive) { hls_params_.playlist_type != HlsPlaylistType::kLive) {
return; return;
} }
DCHECK_GT(time_scale_, 0u); DCHECK_GT(time_scale_, 0u);
// The start time of the latest segment is considered the current_play_time, if (current_buffer_depth_ <= hls_params_.time_shift_buffer_depth)
// 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)
return; 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 // 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 // remove <3> without removing <1> and <2> below (<1> and <2> are moved to the
// temporary list and added back later). // temporary list and added back later).
@ -607,12 +609,17 @@ void MediaPlaylist::SlideWindow() {
++discontinuity_sequence_number_; ++discontinuity_sequence_number_;
} else { } else {
DCHECK_EQ(entry_type, HlsEntry::EntryType::kExtInf); DCHECK_EQ(entry_type, HlsEntry::EntryType::kExtInf);
const SegmentInfoEntry& segment_info = const SegmentInfoEntry& segment_info =
*reinterpret_cast<SegmentInfoEntry*>(last->get()); *reinterpret_cast<SegmentInfoEntry*>(last->get());
const double last_segment_end_time = // Remove the current segment only if it falls completely out of time
segment_info.start_time() + segment_info.duration(); // shift buffer range.
if (timeshift_limit < last_segment_end_time) 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; break;
current_buffer_depth_ -= segment_info.duration();
RemoveOldSegment(segment_info.start_time()); RemoveOldSegment(segment_info.start_time());
media_sequence_number_++; media_sequence_number_++;
} }

View File

@ -240,7 +240,10 @@ class MediaPlaylist {
bool target_duration_set_ = false; bool target_duration_set_ = false;
uint32_t target_duration_ = 0; 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_; 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. // 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. // Once a file is actually removed, it is removed from the list.
std::list<std::string> segments_to_be_removed_; std::list<std::string> segments_to_be_removed_;

View File

@ -79,33 +79,6 @@ uint32_t GetTimeScale(const MediaInfo& media_info) {
return 1; 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 } // namespace
Representation::Representation( Representation::Representation(
@ -203,16 +176,21 @@ void Representation::AddNewSegment(int64_t start_time,
return; 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_) if (state_change_listener_)
state_change_listener_->OnNewSegmentForRepresentation(start_time, duration); state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
AddSegmentInfo(start_time, duration); AddSegmentInfo(start_time, duration);
current_buffer_depth_ += segment_infos_.back().duration;
bandwidth_estimator_.AddBlock( bandwidth_estimator_.AddBlock(
size, static_cast<double>(duration) / media_info_.reference_time_scale()); size, static_cast<double>(duration) / media_info_.reference_time_scale());
SlideWindow();
DCHECK_GE(segment_infos_.size(), 1u);
} }
void Representation::SetSampleDuration(uint32_t frame_duration) { void Representation::SetSampleDuration(uint32_t frame_duration) {
@ -435,7 +413,6 @@ int64_t Representation::AdjustDuration(int64_t duration) const {
} }
void Representation::SlideWindow() { void Representation::SlideWindow() {
DCHECK(!segment_infos_.empty());
if (mpd_options_.mpd_params.time_shift_buffer_depth <= 0.0 || if (mpd_options_.mpd_params.time_shift_buffer_depth <= 0.0 ||
mpd_options_.mpd_type == MpdType::kStatic) mpd_options_.mpd_type == MpdType::kStatic)
return; return;
@ -443,60 +420,40 @@ void Representation::SlideWindow() {
const uint32_t time_scale = GetTimeScale(media_info_); const uint32_t time_scale = GetTimeScale(media_info_);
DCHECK_GT(time_scale, 0u); 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); mpd_options_.mpd_params.time_shift_buffer_depth * time_scale);
// The start time of the latest segment is considered the current_play_time, if (current_buffer_depth_ <= time_shift_buffer_depth)
// 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)
return; 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 first = segment_infos_.begin();
std::list<SegmentInfo>::iterator last = first; std::list<SegmentInfo>::iterator last = first;
for (; last != segment_infos_.end(); ++last) { for (; last != segment_infos_.end(); ++last) {
const int64_t last_segment_end_time = LastSegmentEndTime(*last); // Remove the current segment only if it falls completely out of time shift
if (timeshift_limit < last_segment_end_time) // 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; break;
RemoveSegments(last->start_time, last->duration, last->repeat + 1);
start_number_ += last->repeat + 1;
} }
segment_infos_.erase(first, last); 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, void Representation::RemoveOldSegment(SegmentInfo* segment_info) {
int64_t duration, int64_t segment_start_time = segment_info->start_time;
uint64_t num_segments) { segment_info->start_time += segment_info->duration;
segment_info->repeat--;
if (mpd_options_.mpd_params.preserved_segments_outside_live_window == 0) if (mpd_options_.mpd_params.preserved_segments_outside_live_window == 0)
return; return;
for (size_t i = 0; i < num_segments; ++i) { segments_to_be_removed_.push_back(
segments_to_be_removed_.push_back(media::GetSegmentName( media::GetSegmentName(media_info_.segment_template(), segment_start_time,
media_info_.segment_template(), start_time + i * duration, start_number_ - 1, media_info_.bandwidth()));
start_number_ - 1 + i, media_info_.bandwidth()));
}
while (segments_to_be_removed_.size() > while (segments_to_be_removed_.size() >
mpd_options_.mpd_params.preserved_segments_outside_live_window) { mpd_options_.mpd_params.preserved_segments_outside_live_window) {
VLOG(2) << "Deleting " << segments_to_be_removed_.front(); VLOG(2) << "Deleting " << segments_to_be_removed_.front();

View File

@ -197,10 +197,8 @@ class Representation {
// |start_number_| by the number of segments removed. // |start_number_| by the number of segments removed.
void SlideWindow(); void SlideWindow();
// Remove |num_segments| starting from |start_time| with |duration|. // Remove the first segment in |segment_info|.
void RemoveSegments(int64_t start_time, void RemoveOldSegment(SegmentInfo* segment_info);
int64_t duration,
uint64_t num_segments);
// Note: Because 'mimeType' is a required field for a valid MPD, these return // Note: Because 'mimeType' is a required field for a valid MPD, these return
// strings. // strings.
@ -212,6 +210,8 @@ class Representation {
// any logic using this can assume only one set. // any logic using this can assume only one set.
MediaInfo media_info_; MediaInfo media_info_;
std::list<ContentProtectionElement> content_protection_elements_; std::list<ContentProtectionElement> content_protection_elements_;
int64_t current_buffer_depth_ = 0;
// TODO(kqyang): Address sliding window issue with multiple periods. // TODO(kqyang): Address sliding window issue with multiple periods.
std::list<SegmentInfo> segment_infos_; std::list<SegmentInfo> segment_infos_;
// A list to hold the file names of the segments to be removed temporarily. // A list to hold the file names of the segments to be removed temporarily.

View File

@ -410,7 +410,7 @@ namespace {
// Any number for {AdaptationSet,Representation} ID. Required to create // Any number for {AdaptationSet,Representation} ID. Required to create
// either objects. Not checked in test. // either objects. Not checked in test.
const char kSElementTemplate[] = const char kSElementTemplate[] =
"<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\" r=\"%" PRIu64 "\"/>\n"; "<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\" r=\"%d\"/>\n";
const char kSElementTemplateWithoutR[] = const char kSElementTemplateWithoutR[] =
"<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\"/>\n"; "<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\"/>\n";
const int kDefaultStartNumber = 1; const int kDefaultStartNumber = 1;
@ -457,7 +457,7 @@ class SegmentTemplateTest : public RepresentationTest {
void AddSegments(int64_t start_time, void AddSegments(int64_t start_time,
int64_t duration, int64_t duration,
uint64_t size, uint64_t size,
uint64_t repeat) { int repeat) {
DCHECK(representation_); DCHECK(representation_);
SegmentInfo s = {start_time, duration, repeat}; SegmentInfo s = {start_time, duration, repeat};
@ -470,7 +470,7 @@ class SegmentTemplateTest : public RepresentationTest {
base::StringPrintf(kSElementTemplate, start_time, duration, repeat); base::StringPrintf(kSElementTemplate, start_time, duration, repeat);
} }
for (uint64_t i = 0; i < repeat + 1; ++i) { for (int i = 0; i < repeat + 1; ++i) {
representation_->AddNewSegment(start_time, duration, size); representation_->AddNewSegment(start_time, duration, size);
start_time += duration; start_time += duration;
bandwidth_estimator_.AddBlock( bandwidth_estimator_.AddBlock(
@ -587,7 +587,7 @@ TEST_F(SegmentTemplateTest, NormalRepeatedSegmentDuration) {
const uint64_t kSize = 256; const uint64_t kSize = 256;
int64_t start_time = 0; int64_t start_time = 0;
int64_t duration = 40000; int64_t duration = 40000;
uint64_t repeat = 2; int repeat = 2;
AddSegments(start_time, duration, kSize, repeat); AddSegments(start_time, duration, kSize, repeat);
start_time += duration * (repeat + 1); start_time += duration * (repeat + 1);
@ -607,7 +607,7 @@ TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) {
const uint64_t kSize = 100000; const uint64_t kSize = 100000;
int64_t start_time = 0; int64_t start_time = 0;
int64_t duration = 100000; int64_t duration = 100000;
uint64_t repeat = 2; int repeat = 2;
AddSegments(start_time, duration, kSize, repeat); AddSegments(start_time, duration, kSize, repeat);
start_time += duration * (repeat + 1); start_time += duration * (repeat + 1);
@ -628,8 +628,8 @@ TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) {
TEST_F(SegmentTemplateTest, NonZeroStartTime) { TEST_F(SegmentTemplateTest, NonZeroStartTime) {
const int64_t kStartTime = 10; const int64_t kStartTime = 10;
const int64_t kDuration = 22000; const int64_t kDuration = 22000;
const uint64_t kSize = 123456; const int kSize = 123456;
const uint64_t kRepeat = 1; const int kRepeat = 1;
AddSegments(kStartTime, kDuration, kSize, kRepeat); AddSegments(kStartTime, kDuration, kSize, kRepeat);
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml())); EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
@ -639,8 +639,8 @@ TEST_F(SegmentTemplateTest, NonZeroStartTime) {
TEST_F(SegmentTemplateTest, NonContiguousLiveInfo) { TEST_F(SegmentTemplateTest, NonContiguousLiveInfo) {
const int64_t kStartTime = 10; const int64_t kStartTime = 10;
const int64_t kDuration = 22000; const int64_t kDuration = 22000;
const uint64_t kSize = 123456; const int kSize = 123456;
const uint64_t kRepeat = 0; const int kRepeat = 0;
AddSegments(kStartTime, kDuration, kSize, kRepeat); AddSegments(kStartTime, kDuration, kSize, kRepeat);
const int64_t kStartTimeOffset = 100; const int64_t kStartTimeOffset = 100;
@ -655,8 +655,8 @@ TEST_F(SegmentTemplateTest, OutOfOrder) {
const int64_t kEarlierStartTime = 0; const int64_t kEarlierStartTime = 0;
const int64_t kLaterStartTime = 1000; const int64_t kLaterStartTime = 1000;
const int64_t kDuration = 1000; const int64_t kDuration = 1000;
const uint64_t kSize = 123456; const int kSize = 123456;
const uint64_t kRepeat = 0; const int kRepeat = 0;
AddSegments(kLaterStartTime, kDuration, kSize, kRepeat); AddSegments(kLaterStartTime, kDuration, kSize, kRepeat);
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat); AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
@ -668,8 +668,8 @@ TEST_F(SegmentTemplateTest, OutOfOrder) {
TEST_F(SegmentTemplateTest, OverlappingSegments) { TEST_F(SegmentTemplateTest, OverlappingSegments) {
const int64_t kEarlierStartTime = 0; const int64_t kEarlierStartTime = 0;
const int64_t kDuration = 1000; const int64_t kDuration = 1000;
const uint64_t kSize = 123456; const int kSize = 123456;
const uint64_t kRepeat = 0; const int kRepeat = 0;
const int64_t kOverlappingSegmentStartTime = kDuration / 2; const int64_t kOverlappingSegmentStartTime = kDuration / 2;
CHECK_GT(kDuration, kOverlappingSegmentStartTime); CHECK_GT(kDuration, kOverlappingSegmentStartTime);
@ -686,8 +686,8 @@ TEST_F(SegmentTemplateTest, OverlappingSegments) {
TEST_F(SegmentTemplateTest, OverlappingSegmentsWithinErrorRange) { TEST_F(SegmentTemplateTest, OverlappingSegmentsWithinErrorRange) {
const int64_t kEarlierStartTime = 0; const int64_t kEarlierStartTime = 0;
const int64_t kDuration = 1000; const int64_t kDuration = 1000;
const uint64_t kSize = 123456; const int kSize = 123456;
const uint64_t kRepeat = 0; const int kRepeat = 0;
const int64_t kOverlappingSegmentStartTime = kDuration - 1; const int64_t kOverlappingSegmentStartTime = kDuration - 1;
CHECK_GT(kDuration, kOverlappingSegmentStartTime); CHECK_GT(kDuration, kOverlappingSegmentStartTime);
@ -824,7 +824,7 @@ TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations) {
std::string expected_s_elements; std::string expected_s_elements;
if (allow_approximate_segment_timeline_) { if (allow_approximate_segment_timeline_) {
uint64_t kNumSegments = 3; int kNumSegments = 3;
expected_s_elements = expected_s_elements =
base::StringPrintf(kSElementTemplate, kStartTime, base::StringPrintf(kSElementTemplate, kStartTime,
kScaledTargetSegmentDuration, kNumSegments - 1); kScaledTargetSegmentDuration, kNumSegments - 1);
@ -860,7 +860,7 @@ TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations2) {
"<S t=\"0\" d=\"10\" r=\"1\"/>" "<S t=\"0\" d=\"10\" r=\"1\"/>"
"<S t=\"20\" d=\"13\"/>"; "<S t=\"20\" d=\"13\"/>";
} else { } else {
uint64_t kNumSegments = 3; int kNumSegments = 3;
expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime, expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime,
kDurationLarger, kNumSegments - 1); kDurationLarger, kNumSegments - 1);
} }
@ -879,14 +879,14 @@ TEST_P(ApproximateSegmentTimelineTest, FillSmallGap) {
std::string expected_s_elements; std::string expected_s_elements;
if (allow_approximate_segment_timeline_) { if (allow_approximate_segment_timeline_) {
uint64_t kNumSegments = 3; int kNumSegments = 3;
expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime, expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime,
kDuration, kNumSegments - 1); kDuration, kNumSegments - 1);
} else { } else {
expected_s_elements = expected_s_elements =
base::StringPrintf(kSElementTemplateWithoutR, kStartTime, kDuration) + base::StringPrintf(kSElementTemplateWithoutR, kStartTime, kDuration) +
base::StringPrintf(kSElementTemplate, kStartTime + kDuration + kGap, base::StringPrintf(kSElementTemplate, kStartTime + kDuration + kGap,
kDuration, static_cast<uint64_t>(1) /* repeat */); kDuration, 1 /* repeat */);
} }
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml().get(),
XmlNodeEqual(ExpectedXml(expected_s_elements))); XmlNodeEqual(ExpectedXml(expected_s_elements)));
@ -903,14 +903,14 @@ TEST_P(ApproximateSegmentTimelineTest, FillSmallOverlap) {
std::string expected_s_elements; std::string expected_s_elements;
if (allow_approximate_segment_timeline_) { if (allow_approximate_segment_timeline_) {
uint64_t kNumSegments = 3; int kNumSegments = 3;
expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime, expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime,
kDuration, kNumSegments - 1); kDuration, kNumSegments - 1);
} else { } else {
expected_s_elements = expected_s_elements =
base::StringPrintf(kSElementTemplateWithoutR, kStartTime, kDuration) + base::StringPrintf(kSElementTemplateWithoutR, kStartTime, kDuration) +
base::StringPrintf(kSElementTemplate, kStartTime + kDuration - kOverlap, base::StringPrintf(kSElementTemplate, kStartTime + kDuration - kOverlap,
kDuration, static_cast<uint64_t>(1) /* repeat */); kDuration, 1 /* repeat */);
} }
EXPECT_THAT(representation_->GetXml().get(), EXPECT_THAT(representation_->GetXml().get(),
XmlNodeEqual(ExpectedXml(expected_s_elements))); XmlNodeEqual(ExpectedXml(expected_s_elements)));
@ -979,8 +979,8 @@ TEST_P(TimeShiftBufferDepthTest, Normal) {
// Trick to make every segment 1 second long. // Trick to make every segment 1 second long.
const int64_t kDuration = kDefaultTimeScale; const int64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 10000; const uint64_t kSize = 10000;
const uint64_t kRepeat = 1234; const int kRepeat = 1234;
const uint64_t kLength = kRepeat; const int kLength = kRepeat;
CHECK_EQ(kDuration / kDefaultTimeScale * kRepeat, kLength); CHECK_EQ(kDuration / kDefaultTimeScale * kRepeat, kLength);
@ -997,7 +997,7 @@ TEST_P(TimeShiftBufferDepthTest, Normal) {
const std::string expected_s_element = base::StringPrintf( const std::string expected_s_element = base::StringPrintf(
kSElementTemplate, kSElementTemplate,
initial_start_time_ + kDuration * (kRepeat - kExpectedRepeatsLeft), initial_start_time_ + kDuration * (kRepeat - kExpectedRepeatsLeft),
kDuration, static_cast<uint64_t>(kExpectedRepeatsLeft)); kDuration, kExpectedRepeatsLeft);
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml().get(),
XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber))); XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber)));
@ -1015,8 +1015,8 @@ TEST_P(TimeShiftBufferDepthTest, TimeShiftBufferDepthShorterThanSegmentLength) {
// Each duration is a second longer than timeShiftBufferDepth. // Each duration is a second longer than timeShiftBufferDepth.
const int64_t kDuration = kDefaultTimeScale * (kTimeShiftBufferDepth + 1); const int64_t kDuration = kDefaultTimeScale * (kTimeShiftBufferDepth + 1);
const uint64_t kSize = 10000; const int kSize = 10000;
const uint64_t kRepeat = 1; const int kRepeat = 1;
AddSegments(initial_start_time_, kDuration, kSize, kRepeat); AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
@ -1034,8 +1034,8 @@ TEST_P(TimeShiftBufferDepthTest, Generic) {
kTimeShiftBufferDepth; kTimeShiftBufferDepth;
const int64_t kDuration = kDefaultTimeScale; const int64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 10000; const int kSize = 10000;
const uint64_t kRepeat = 1000; const int kRepeat = 1000;
AddSegments(initial_start_time_, kDuration, kSize, kRepeat); AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
const int64_t first_s_element_end_time = const int64_t first_s_element_end_time =
@ -1052,8 +1052,7 @@ TEST_P(TimeShiftBufferDepthTest, Generic) {
// Expect only the latest S element with 2 segments. // Expect only the latest S element with 2 segments.
const std::string expected_s_element = const std::string expected_s_element =
base::StringPrintf(kSElementTemplate, first_s_element_end_time, base::StringPrintf(kSElementTemplate, first_s_element_end_time,
kTimeShiftBufferDepthDuration, kTimeShiftBufferDepthDuration, kMoreSegmentsRepeat);
static_cast<uint64_t>(kMoreSegmentsRepeat));
const int kExpectedRemovedSegments = kRepeat + 1; const int kExpectedRemovedSegments = kRepeat + 1;
EXPECT_THAT( EXPECT_THAT(
@ -1072,21 +1071,21 @@ TEST_P(TimeShiftBufferDepthTest, MoreThanOneS) {
mutable_mpd_options()->mpd_params.time_shift_buffer_depth = mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth; kTimeShiftBufferDepth;
const uint64_t kSize = 20000; const int kSize = 20000;
const int64_t kOneSecondDuration = kDefaultTimeScale; const int64_t kOneSecondDuration = kDefaultTimeScale;
const uint64_t kOneSecondSegmentRepeat = 99; const int kOneSecondSegmentRepeat = 99;
AddSegments(initial_start_time_, kOneSecondDuration, kSize, AddSegments(initial_start_time_, kOneSecondDuration, kSize,
kOneSecondSegmentRepeat); kOneSecondSegmentRepeat);
const int64_t first_s_element_end_time = const int64_t first_s_element_end_time =
initial_start_time_ + kOneSecondDuration * (kOneSecondSegmentRepeat + 1); initial_start_time_ + kOneSecondDuration * (kOneSecondSegmentRepeat + 1);
const int64_t kTwoSecondDuration = 2 * kDefaultTimeScale; const int64_t kTwoSecondDuration = 2 * kDefaultTimeScale;
const uint64_t kTwoSecondSegmentRepeat = 20; const int kTwoSecondSegmentRepeat = 20;
AddSegments(first_s_element_end_time, kTwoSecondDuration, kSize, AddSegments(first_s_element_end_time, kTwoSecondDuration, kSize,
kTwoSecondSegmentRepeat); kTwoSecondSegmentRepeat);
const uint64_t kExpectedRemovedSegments = const int kExpectedRemovedSegments =
(kOneSecondSegmentRepeat + 1 + kTwoSecondSegmentRepeat * 2) - (kOneSecondSegmentRepeat + 1 + kTwoSecondSegmentRepeat * 2) -
kTimeShiftBufferDepth; kTimeShiftBufferDepth;
@ -1118,8 +1117,8 @@ TEST_P(TimeShiftBufferDepthTest, UseLastSegmentInS) {
kTimeShiftBufferDepth; kTimeShiftBufferDepth;
const int64_t kDuration1 = static_cast<int64_t>(kDefaultTimeScale * 1.5); const int64_t kDuration1 = static_cast<int64_t>(kDefaultTimeScale * 1.5);
const uint64_t kSize = 20000; const int kSize = 20000;
const uint64_t kRepeat1 = 1; const int kRepeat1 = 1;
AddSegments(initial_start_time_, kDuration1, kSize, kRepeat1); AddSegments(initial_start_time_, kDuration1, kSize, kRepeat1);
@ -1127,7 +1126,7 @@ TEST_P(TimeShiftBufferDepthTest, UseLastSegmentInS) {
initial_start_time_ + kDuration1 * (kRepeat1 + 1); initial_start_time_ + kDuration1 * (kRepeat1 + 1);
const int64_t kTwoSecondDuration = 2 * kDefaultTimeScale; const int64_t kTwoSecondDuration = 2 * kDefaultTimeScale;
const uint64_t kTwoSecondSegmentRepeat = 4; const int kTwoSecondSegmentRepeat = 4;
AddSegments(first_s_element_end_time, kTwoSecondDuration, kSize, AddSegments(first_s_element_end_time, kTwoSecondDuration, kSize,
kTwoSecondSegmentRepeat); kTwoSecondSegmentRepeat);
@ -1151,8 +1150,8 @@ TEST_P(TimeShiftBufferDepthTest, NormalGap) {
kTimeShiftBufferDepth; kTimeShiftBufferDepth;
const int64_t kDuration = kDefaultTimeScale; const int64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 20000; const int kSize = 20000;
const uint64_t kRepeat = 6; const int kRepeat = 6;
// CHECK here so that the when next S element is added with 1 segment, this S // CHECK here so that the when next S element is added with 1 segment, this S
// element doesn't go away. // element doesn't go away.
CHECK_LT(kRepeat - 1u, static_cast<uint64_t>(kTimeShiftBufferDepth)); CHECK_LT(kRepeat - 1u, static_cast<uint64_t>(kTimeShiftBufferDepth));
@ -1176,15 +1175,15 @@ TEST_P(TimeShiftBufferDepthTest, NormalGap) {
XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber))); XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber)));
} }
// Case where there is a huge gap so the first S element is removed. // Timeshift is based on segment duration not on segment time.
TEST_P(TimeShiftBufferDepthTest, HugeGap) { TEST_P(TimeShiftBufferDepthTest, HugeGap) {
const int kTimeShiftBufferDepth = 10; const int kTimeShiftBufferDepth = 10;
mutable_mpd_options()->mpd_params.time_shift_buffer_depth = mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth; kTimeShiftBufferDepth;
const int64_t kDuration = kDefaultTimeScale; const int64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 20000; const int kSize = 20000;
const uint64_t kRepeat = 6; const int kRepeat = 6;
AddSegments(initial_start_time_, kDuration, kSize, kRepeat); AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
const int64_t first_s_element_end_time = const int64_t first_s_element_end_time =
@ -1194,7 +1193,7 @@ TEST_P(TimeShiftBufferDepthTest, HugeGap) {
const int64_t gap_s_element_start_time = const int64_t gap_s_element_start_time =
first_s_element_end_time + first_s_element_end_time +
(kTimeShiftBufferDepth + 1) * kDefaultTimeScale; (kTimeShiftBufferDepth + 1) * kDefaultTimeScale;
const uint64_t kSecondSElementRepeat = 9; const int kSecondSElementRepeat = 9;
static_assert( static_assert(
kSecondSElementRepeat < static_cast<int64_t>(kTimeShiftBufferDepth), kSecondSElementRepeat < static_cast<int64_t>(kTimeShiftBufferDepth),
"second_s_element_repeat_must_be_less_than_time_shift_buffer_depth"); "second_s_element_repeat_must_be_less_than_time_shift_buffer_depth");
@ -1202,9 +1201,11 @@ TEST_P(TimeShiftBufferDepthTest, HugeGap) {
kSecondSElementRepeat); kSecondSElementRepeat);
std::string expected_s_element = std::string expected_s_element =
base::StringPrintf(kSElementTemplateWithoutR,
initial_start_time_ + kRepeat * kDuration, kDuration) +
base::StringPrintf(kSElementTemplate, gap_s_element_start_time, kDuration, base::StringPrintf(kSElementTemplate, gap_s_element_start_time, kDuration,
kSecondSElementRepeat); kSecondSElementRepeat);
const int kExpectedRemovedSegments = kRepeat + 1; const int kExpectedRemovedSegments = kRepeat;
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml().get(),
XmlNodeEqual(ExpectedXml( XmlNodeEqual(ExpectedXml(
@ -1218,9 +1219,9 @@ TEST_P(TimeShiftBufferDepthTest, ManySegments) {
kTimeShiftBufferDepth; kTimeShiftBufferDepth;
const int64_t kDuration = kDefaultTimeScale; const int64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 20000; const int kSize = 20000;
const uint64_t kRepeat = 10000; const int kRepeat = 10000;
const uint64_t kTotalNumSegments = kRepeat + 1; const int kTotalNumSegments = kRepeat + 1;
AddSegments(initial_start_time_, kDuration, kSize, kRepeat); AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
const int kExpectedSegmentsLeft = kTimeShiftBufferDepth + 1; const int kExpectedSegmentsLeft = kTimeShiftBufferDepth + 1;
@ -1233,7 +1234,7 @@ TEST_P(TimeShiftBufferDepthTest, ManySegments) {
std::string expected_s_element = base::StringPrintf( std::string expected_s_element = base::StringPrintf(
kSElementTemplate, kSElementTemplate,
initial_start_time_ + kExpectedRemovedSegments * kDuration, kDuration, initial_start_time_ + kExpectedRemovedSegments * kDuration, kDuration,
static_cast<uint64_t>(kExpectedSegmentsRepeat)); kExpectedSegmentsRepeat);
EXPECT_THAT( EXPECT_THAT(
representation_->GetXml().get(), representation_->GetXml().get(),
XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber))); XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber)));
@ -1327,7 +1328,7 @@ TEST_F(RepresentationDeleteSegmentsTest, ManyNonRepeatingSegments) {
// Verify that segments are deleted as expected with many repeating segments. // Verify that segments are deleted as expected with many repeating segments.
TEST_F(RepresentationDeleteSegmentsTest, ManyRepeatingSegments) { TEST_F(RepresentationDeleteSegmentsTest, ManyRepeatingSegments) {
const int kLoops = 4; const int kLoops = 4;
const uint64_t kRepeat = 10; const int kRepeat = 10;
for (int i = 0; i < kLoops; ++i) { for (int i = 0; i < kLoops; ++i) {
AddSegments(kInitialStartTime + i * kDuration * (kRepeat + 1), kDuration, AddSegments(kInitialStartTime + i * kDuration * (kRepeat + 1), kDuration,
kSize, kRepeat); kSize, kRepeat);

View File

@ -19,7 +19,7 @@ struct SegmentInfo {
// |start_time| and has |duration| but none others have |start_time| * N and // |start_time| and has |duration| but none others have |start_time| * N and
// |duration|, then this should be set to 0. The semantics is the same as S@r // |duration|, then this should be set to 0. The semantics is the same as S@r
// in the DASH MPD spec. // in the DASH MPD spec.
uint64_t repeat; int repeat;
}; };
} // namespace shaka } // namespace shaka