[HLS] Fix live chunks not deleted with $Time$ in segment template

Store and use the segment start time in the original time scale to
delete old segments, instead of storing it in seconds, as the segment
file name is generated using the time with the time scale.

Fixes #625.

Change-Id: I7524d597b1ffc081dd1399d6fb3ea47c13502881
This commit is contained in:
KongQun Yang 2019-08-07 16:14:45 -07:00
parent faa9a3ea68
commit 9029e2d34f
3 changed files with 82 additions and 54 deletions

View File

@ -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<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);
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<SegmentInfoEntry*>(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<SegmentInfoEntry*>(iter->get());
const double segment_duration_seconds =
next_timestamp_seconds - segment_info->start_time();
next_timestamp_seconds -
static_cast<double>(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_++;
}

View File

@ -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_;

View File

@ -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<std::pair<std::string, std::string>> {
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, FileCloser> 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<std::pair<std::string, std::string>> {};