diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 40a3983b5d..7544cca7d6 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -162,27 +162,30 @@ class SegmentInfoEntry : public HlsEntry { // after EXTINF. // It uses |previous_segment_end_offset| to determine if it has to also // specify the start byte offset in the tag. - // |duration| is duration in seconds. + // |start_time| is in timescale. + // |duration_seconds| is duration in seconds. SegmentInfoEntry(const std::string& file_name, - double start_time, - double duration, + int64_t start_time, + double duration_seconds, bool use_byte_range, uint64_t start_byte_offset, uint64_t segment_file_size, uint64_t previous_segment_end_offset); std::string ToString() override; - double start_time() const { return start_time_; } - double duration() const { return duration_; } - void set_duration(double duration) { duration_ = duration; } + int64_t start_time() const { return start_time_; } + double duration_seconds() const { return duration_seconds_; } + void set_duration_seconds(double duration_seconds) { + duration_seconds_ = duration_seconds; + } private: SegmentInfoEntry(const SegmentInfoEntry&) = delete; SegmentInfoEntry& operator=(const SegmentInfoEntry&) = delete; const std::string file_name_; - const double start_time_; - double duration_; + const int64_t start_time_; + double duration_seconds_; const bool use_byte_range_; const uint64_t start_byte_offset_; const uint64_t segment_file_size_; @@ -190,8 +193,8 @@ class SegmentInfoEntry : public HlsEntry { }; SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name, - double start_time, - double duration, + int64_t start_time, + double duration_seconds, bool use_byte_range, uint64_t start_byte_offset, uint64_t segment_file_size, @@ -199,14 +202,14 @@ SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name, : HlsEntry(HlsEntry::EntryType::kExtInf), file_name_(file_name), start_time_(start_time), - duration_(duration), + duration_seconds_(duration_seconds), use_byte_range_(use_byte_range), start_byte_offset_(start_byte_offset), segment_file_size_(segment_file_size), previous_segment_end_offset_(previous_segment_end_offset) {} std::string SegmentInfoEntry::ToString() { - std::string result = base::StringPrintf("#EXTINF:%.3f,", duration_); + std::string result = base::StringPrintf("#EXTINF:%.3f,", duration_seconds_); if (use_byte_range_) { base::StringAppendF(&result, "\n#EXT-X-BYTERANGE:%" PRIu64, @@ -488,7 +491,7 @@ uint64_t MediaPlaylist::AvgBitrate() const { } double MediaPlaylist::GetLongestSegmentDuration() const { - return longest_segment_duration_; + return longest_segment_duration_seconds_; } void MediaPlaylist::SetTargetDuration(uint32_t target_duration) { @@ -546,12 +549,10 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name, // durations (in the manifest/playlist) after sliding the window. SlideWindow(); - const double start_time_seconds = - static_cast(start_time) / time_scale_; const double segment_duration_seconds = static_cast(duration) / time_scale_; - longest_segment_duration_ = - std::max(longest_segment_duration_, segment_duration_seconds); + longest_segment_duration_seconds_ = + std::max(longest_segment_duration_seconds_, segment_duration_seconds); bandwidth_estimator_.AddBlock(size, segment_duration_seconds); current_buffer_depth_ += segment_duration_seconds; @@ -559,18 +560,18 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name, entries_.back()->type() == HlsEntry::EntryType::kExtInf) { const SegmentInfoEntry* segment_info = static_cast(entries_.back().get()); - if (segment_info->start_time() > start_time_seconds) { + if (segment_info->start_time() > start_time) { 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 << "."; + << start_time << "."; 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_)); + segment_file_name, start_time, segment_duration_seconds, use_byte_range_, + start_byte_offset, size, previous_segment_end_offset_)); previous_segment_end_offset_ = start_byte_offset + size - 1; } @@ -587,17 +588,26 @@ void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) { reinterpret_cast(iter->get()); const double segment_duration_seconds = - next_timestamp_seconds - segment_info->start_time(); + next_timestamp_seconds - + static_cast(segment_info->start_time()) / time_scale_; // 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); + segment_info->set_duration_seconds(segment_duration_seconds); + longest_segment_duration_seconds_ = + std::max(longest_segment_duration_seconds_, segment_duration_seconds); break; } } } +// TODO(kqyang): Right now this class manages the segments including the +// deletion of segments when it is no longer needed. However, this class does +// not have access to the segment file paths, which is already translated to +// segment URLs by HlsNotifier. We have to re-generate segment file paths from +// segment template here in order to delete the old segments. +// To make the pipeline cleaner, we should move all file manipulations including +// segment management to an intermediate layer between HlsNotifier and +// MediaPlaylist. void MediaPlaylist::SlideWindow() { if (hls_params_.time_shift_buffer_depth <= 0.0 || hls_params_.playlist_type != HlsPlaylistType::kLive) { @@ -637,11 +647,11 @@ void MediaPlaylist::SlideWindow() { // 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() < + current_buffer_depth_ - segment_info.duration_seconds() < hls_params_.time_shift_buffer_depth; if (segment_within_time_shift_buffer) break; - current_buffer_depth_ -= segment_info.duration(); + current_buffer_depth_ -= segment_info.duration_seconds(); RemoveOldSegment(segment_info.start_time()); media_sequence_number_++; } diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index 9d5e22c7cd..b0a0823f8e 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -228,7 +228,7 @@ class MediaPlaylist { bool inserted_discontinuity_tag_ = false; int discontinuity_sequence_number_ = 0; - double longest_segment_duration_ = 0.0; + double longest_segment_duration_seconds_ = 0.0; uint32_t time_scale_ = 0; BandwidthEstimator bandwidth_estimator_; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 46f00e678a..10d4fcad70 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -940,80 +940,98 @@ const int kNumPreservedSegmentsOutsideLiveWindow = 3; const int kMaxNumSegmentsAvailable = kTimeShiftBufferDepth + 1 + kNumPreservedSegmentsOutsideLiveWindow; -const char kSegmentTemplate[] = "memory://$Number$.mp4"; -const char kSegmentTemplateUrl[] = "video/$Number$.mp4"; +const char kSegmentTemplateNumber[] = "memory://$Number$.mp4"; +const char kSegmentTemplateNumberUrl[] = "video/$Number$.mp4"; const char kStringPrintTemplate[] = "memory://%d.mp4"; const char kIgnoredSegmentName[] = "ignored_segment_name"; +const char kSegmentTemplateTime[] = "memory://$Time$.mp4"; +const char kSegmentTemplateTimeUrl[] = "video/$Time$.mp4"; + const uint64_t kInitialStartTime = 0; const uint64_t kDuration = kTimeScale; } // namespace -class MediaPlaylistDeleteSegmentsTest : public LiveMediaPlaylistTest { +class MediaPlaylistDeleteSegmentsTest + : public LiveMediaPlaylistTest, + public WithParamInterface> { public: void SetUp() override { LiveMediaPlaylistTest::SetUp(); + std::tie(segment_template_, segment_template_url_) = GetParam(); + // Create 100 files with the template. - for (int i = 1; i <= 100; ++i) { - File::WriteStringToFile( - base::StringPrintf(kStringPrintTemplate, i).c_str(), "dummy content"); + for (int i = 0; i < 100; ++i) { + File::WriteStringToFile(GetSegmentName(i).c_str(), "dummy content"); } - valid_video_media_info_.set_segment_template(kSegmentTemplate); - valid_video_media_info_.set_segment_template_url(kSegmentTemplateUrl); + valid_video_media_info_.set_segment_template(segment_template_); + valid_video_media_info_.set_segment_template_url(segment_template_url_); ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_)); mutable_hls_params()->preserved_segments_outside_live_window = kNumPreservedSegmentsOutsideLiveWindow; } + int GetTime(int index) const { return kInitialStartTime + index * kDuration; } + + std::string GetSegmentName(int index) { + if (segment_template_.find("$Time$") != std::string::npos) + return base::StringPrintf(kStringPrintTemplate, GetTime(index)); + return base::StringPrintf(kStringPrintTemplate, index + 1); + } + bool SegmentDeleted(const std::string& segment_name) { std::unique_ptr file_closer( File::Open(segment_name.c_str(), "r")); return file_closer.get() == nullptr; } + + private: + std::string segment_template_; + std::string segment_template_url_; }; // Verify that no segments are deleted initially until there are more than // |kMaxNumSegmentsAvailable| segments. -TEST_F(MediaPlaylistDeleteSegmentsTest, NoSegmentsDeletedInitially) { +TEST_P(MediaPlaylistDeleteSegmentsTest, NoSegmentsDeletedInitially) { for (int i = 0; i < kMaxNumSegmentsAvailable; ++i) { - media_playlist_->AddSegment(kIgnoredSegmentName, - kInitialStartTime + i * kDuration, kDuration, + media_playlist_->AddSegment(kIgnoredSegmentName, GetTime(i), kDuration, kZeroByteOffset, kMBytes); } for (int i = 0; i < kMaxNumSegmentsAvailable; ++i) { - EXPECT_FALSE( - SegmentDeleted(base::StringPrintf(kStringPrintTemplate, i + 1))); + EXPECT_FALSE(SegmentDeleted(GetSegmentName(i))); } } -TEST_F(MediaPlaylistDeleteSegmentsTest, OneSegmentDeleted) { +TEST_P(MediaPlaylistDeleteSegmentsTest, OneSegmentDeleted) { for (int i = 0; i <= kMaxNumSegmentsAvailable; ++i) { - media_playlist_->AddSegment(kIgnoredSegmentName, - kInitialStartTime + i * kDuration, kDuration, + media_playlist_->AddSegment(kIgnoredSegmentName, GetTime(i), kDuration, kZeroByteOffset, kMBytes); } - EXPECT_FALSE(SegmentDeleted(base::StringPrintf(kStringPrintTemplate, 2))); - EXPECT_TRUE(SegmentDeleted(base::StringPrintf(kStringPrintTemplate, 1))); + EXPECT_FALSE(SegmentDeleted(GetSegmentName(1))); + EXPECT_TRUE(SegmentDeleted(GetSegmentName(0))); } -TEST_F(MediaPlaylistDeleteSegmentsTest, ManySegments) { +TEST_P(MediaPlaylistDeleteSegmentsTest, ManySegments) { int many_segments = 50; for (int i = 0; i < many_segments; ++i) { - media_playlist_->AddSegment(kIgnoredSegmentName, - kInitialStartTime + i * kDuration, kDuration, + media_playlist_->AddSegment(kIgnoredSegmentName, GetTime(i), kDuration, kZeroByteOffset, kMBytes); } const int last_available_segment_index = - many_segments - kMaxNumSegmentsAvailable + 1; - EXPECT_FALSE(SegmentDeleted( - base::StringPrintf(kStringPrintTemplate, last_available_segment_index))); - EXPECT_TRUE(SegmentDeleted(base::StringPrintf( - kStringPrintTemplate, last_available_segment_index - 1))); + many_segments - kMaxNumSegmentsAvailable; + EXPECT_FALSE(SegmentDeleted(GetSegmentName(last_available_segment_index))); + EXPECT_TRUE(SegmentDeleted(GetSegmentName(last_available_segment_index - 1))); } +INSTANTIATE_TEST_CASE_P( + TimeOrNumber, + MediaPlaylistDeleteSegmentsTest, + Values(std::make_pair(kSegmentTemplateNumber, kSegmentTemplateNumberUrl), + std::make_pair(kSegmentTemplateTime, kSegmentTemplateTimeUrl))); + class MediaPlaylistCodecTest : public MediaPlaylistTest, public WithParamInterface> {};