Add logic to correctly set subSegmentAlignment for VOD

- subSegmentAlignment logic assumed that all Representations were
  added before calling Representations::AddNewSegment() on any
  Representation in an AdaptationSet. It is not a valid assumption
  for VOD and is removed.

Change-Id: Ifb7e34ee103ee30027e45a804427baf281f3137c
This commit is contained in:
Rintaro Kuroiwa 2015-08-05 14:44:35 -07:00
parent e5b6096857
commit bb8cf87617
3 changed files with 116 additions and 28 deletions

View File

@ -10,6 +10,7 @@
#include <libxml/xmlstring.h> #include <libxml/xmlstring.h>
#include <cmath> #include <cmath>
#include <iterator>
#include <list> #include <list>
#include <string> #include <string>
@ -732,6 +733,11 @@ xml::ScopedXmlPtr<xmlNode>::type AdaptationSet::GetXml() {
video_frame_rates_.rbegin()->second); video_frame_rates_.rbegin()->second);
} }
// Note: must be checked before checking segments_aligned_ (below).
if (mpd_type_ == MpdBuilder::kStatic) {
CheckVodSegmentAlignment();
}
if (segments_aligned_ == kSegmentAlignmentTrue) { if (segments_aligned_ == kSegmentAlignmentTrue) {
adaptation_set.SetStringAttribute(mpd_type_ == MpdBuilder::kStatic adaptation_set.SetStringAttribute(mpd_type_ == MpdBuilder::kStatic
? "subSegmentAlignment" ? "subSegmentAlignment"
@ -767,11 +773,24 @@ int AdaptationSet::Group() const {
return group_; return group_;
} }
// Check segmentAlignment for Live here. Storing all start_time and duration
// will out-of-memory because there's no way of knowing when it will end.
// VOD subSegmentAlignment check is *not* done here because it is possible
// that some Representations might not have been added yet (e.g. a thread is
// assigned per muxer so one might run faster than others).
// To be clear, for Live, all Representations should be added before a
// segment is added.
void AdaptationSet::OnNewSegmentForRepresentation(uint32_t representation_id, void AdaptationSet::OnNewSegmentForRepresentation(uint32_t representation_id,
uint64_t start_time, uint64_t start_time,
uint64_t duration) { uint64_t duration) {
base::AutoLock scoped_lock(lock_); base::AutoLock scoped_lock(lock_);
CheckSegmentAlignment(representation_id, start_time, duration);
if (mpd_type_ == MpdBuilder::kDynamic) {
CheckLiveSegmentAlignment(representation_id, start_time, duration);
} else {
representation_segment_start_times_[representation_id].push_back(
start_time);
}
} }
void AdaptationSet::OnSetFrameRateForRepresentation( void AdaptationSet::OnSetFrameRateForRepresentation(
@ -827,12 +846,9 @@ bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) {
// They are not aligned but this will be marked as aligned. // They are not aligned but this will be marked as aligned.
// But since this is unlikely to happen in the packager (and to save // But since this is unlikely to happen in the packager (and to save
// computation), this isn't handled at the moment. // computation), this isn't handled at the moment.
// TODO(rkuroiwa): For VOD, not all Representations get added to an void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id,
// AdaptationSet before this is called. Add a similar but separate method that uint64_t start_time,
// keeps the timestamps around. It shouldn't out-of-memory for VOD. uint64_t /* duration */) {
void AdaptationSet::CheckSegmentAlignment(uint32_t representation_id,
uint64_t start_time,
uint64_t /* duration */) {
if (segments_aligned_ == kSegmentAlignmentFalse || if (segments_aligned_ == kSegmentAlignmentFalse ||
force_set_segment_alignment_) { force_set_segment_alignment_) {
return; return;
@ -874,6 +890,66 @@ void AdaptationSet::CheckSegmentAlignment(uint32_t representation_id,
} }
} }
// Make sure all segements start times match for all Representations.
// This assumes that the segments are contiguous.
void AdaptationSet::CheckVodSegmentAlignment() {
if (segments_aligned_ == kSegmentAlignmentFalse ||
force_set_segment_alignment_) {
return;
}
if (representation_segment_start_times_.empty())
return;
if (representation_segment_start_times_.size() == 1) {
segments_aligned_ = kSegmentAlignmentTrue;
return;
}
// This is not the most efficient implementation to compare the values
// because expected_time_line is compared against all other time lines, but
// probably the most readable.
const std::list<uint64_t>& expected_time_line =
representation_segment_start_times_.begin()->second;
bool all_segment_time_line_same_length = true;
// Note that the first entry is skipped because it is expected_time_line.
RepresentationTimeline::const_iterator it =
representation_segment_start_times_.begin();
for (++it; it != representation_segment_start_times_.end(); ++it) {
const std::list<uint64_t>& other_time_line = it->second;
if (expected_time_line.size() != other_time_line.size()) {
all_segment_time_line_same_length = false;
}
const std::list<uint64_t>* longer_list = &other_time_line;
const std::list<uint64_t>* shorter_list = &expected_time_line;
if (expected_time_line.size() > other_time_line.size()) {
shorter_list = &other_time_line;
longer_list = &expected_time_line;
}
if (!std::equal(shorter_list->begin(), shorter_list->end(),
longer_list->begin())) {
// Some segments are definitely unaligned.
segments_aligned_ = kSegmentAlignmentFalse;
representation_segment_start_times_.clear();
return;
}
}
// TODO(rkuroiwa): The right way to do this is to also check the durations.
// For example:
// (a) 3 4 5
// (b) 3 4 5 6
// could be true or false depending on the length of the third segment of (a).
// i.e. if length of the third segment is 2, then this is not aligned.
if (!all_segment_time_line_same_length) {
segments_aligned_ = kSegmentAlignmentUnknown;
return;
}
segments_aligned_ = kSegmentAlignmentTrue;
}
// Since all AdaptationSet cares about is the maxFrameRate, representation_id // Since all AdaptationSet cares about is the maxFrameRate, representation_id
// is not passed to this method. // is not passed to this method.
void AdaptationSet::RecordFrameRate(uint32_t frame_duration, void AdaptationSet::RecordFrameRate(uint32_t frame_duration,

View File

@ -312,13 +312,18 @@ class AdaptationSet {
/// Called from OnNewSegmentForRepresentation(). Checks whether the segments /// Called from OnNewSegmentForRepresentation(). Checks whether the segments
/// are aligned. Sets segments_aligned_. /// are aligned. Sets segments_aligned_.
/// This is only for Live. For VOD, CheckVodSegmentAlignment() should be used.
/// @param representation_id is the id of the Representation with a new /// @param representation_id is the id of the Representation with a new
/// segment. /// segment.
/// @param start_time is the start time of the new segment. /// @param start_time is the start time of the new segment.
/// @param duration is the duration of the new segment. /// @param duration is the duration of the new segment.
void CheckSegmentAlignment(uint32_t representation_id, void CheckLiveSegmentAlignment(uint32_t representation_id,
uint64_t start_time, uint64_t start_time,
uint64_t duration); uint64_t duration);
// Checks representation_segment_start_times_ and sets segments_aligned_.
// Use this for VOD, do not use for Live.
void CheckVodSegmentAlignment();
// Records the framerate of a Representation. // Records the framerate of a Representation.
void RecordFrameRate(uint32_t frame_duration, uint32_t timescale); void RecordFrameRate(uint32_t frame_duration, uint32_t timescale);
@ -377,6 +382,12 @@ class AdaptationSet {
bool force_set_segment_alignment_; bool force_set_segment_alignment_;
// Keeps track of segment start times of Representations. // Keeps track of segment start times of Representations.
// For VOD, this will not be cleared, all the segment start times are
// stored in this. This should not out-of-memory for a reasonable length
// video and reasonable subsegment length.
// For Live, the entries are deleted (see CheckLiveSegmentAlignment()
// implementation comment) because storing the entire timeline is not
// reasonable and may cause an out-of-memory problem.
RepresentationTimeline representation_segment_start_times_; RepresentationTimeline representation_segment_start_times_;
DISALLOW_COPY_AND_ASSIGN(AdaptationSet); DISALLOW_COPY_AND_ASSIGN(AdaptationSet);

View File

@ -947,6 +947,8 @@ TEST_F(CommonMpdBuilderTest,
// Verify that subSegmentAlignment is set to true if all the Representations' // Verify that subSegmentAlignment is set to true if all the Representations'
// segments are aligned and the MPD type is static. // segments are aligned and the MPD type is static.
// Also checking that not all Representations have to be added before calling
// AddNewSegment() on a Representation.
TEST_F(StaticMpdBuilderTest, SubSegmentAlignment) { TEST_F(StaticMpdBuilderTest, SubSegmentAlignment) {
base::AtomicSequenceNumber sequence_counter; base::AtomicSequenceNumber sequence_counter;
const char k480pMediaInfo[] = const char k480pMediaInfo[] =
@ -971,37 +973,36 @@ TEST_F(StaticMpdBuilderTest, SubSegmentAlignment) {
" pixel_height: 1\n" " pixel_height: 1\n"
"}\n" "}\n"
"container_type: 1\n"; "container_type: 1\n";
// First use same start time and duration, and verify that subSegmentAlignment
// is set to true.
const uint64_t kStartTime = 0u;
const uint64_t kDuration = 10u;
const uint64_t kAnySize = 19834u;
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
MpdBuilder::kStatic, &sequence_counter); MpdBuilder::kStatic, &sequence_counter);
Representation* representation_480p = Representation* representation_480p =
adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
// Add a subsegment immediately before adding the 360p Representation.
// This should still work for VOD.
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize);
Representation* representation_360p = Representation* representation_360p =
adaptation_set.AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); adaptation_set.AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
// First use same start time and duration, and verify that that
// segmentAlignment is set.
const uint64_t kStartTime = 0u;
const uint64_t kDuration = 10u;
const uint64_t kAnySize = 19834u;
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize);
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
xml::ScopedXmlPtr<xmlNode>::type aligned(adaptation_set.GetXml()); xml::ScopedXmlPtr<xmlNode>::type aligned(adaptation_set.GetXml());
EXPECT_NO_FATAL_FAILURE( EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("subSegmentAlignment", "true", aligned.get())); ExpectAttributeEqString("subSegmentAlignment", "true", aligned.get()));
EXPECT_EQ(2u, adaptation_set.representation_segment_start_times_.size());
// Also check that the start times are removed from the vector. // Unknown because 480p has an extra subsegments.
EXPECT_EQ(0u, representation_480p->AddNewSegment(11, 20, kAnySize);
adaptation_set xml::ScopedXmlPtr<xmlNode>::type alignment_unknown(adaptation_set.GetXml());
.representation_segment_start_times_[representation_480p->id()] EXPECT_NO_FATAL_FAILURE(
.size()); ExpectAttributeNotSet("subSegmentAlignment", alignment_unknown.get()));
EXPECT_EQ(0u,
adaptation_set
.representation_segment_start_times_[representation_360p->id()]
.size());
// Add segments that make them not aligned. // Add segments that make them not aligned.
representation_480p->AddNewSegment(11, 20, kAnySize);
representation_360p->AddNewSegment(10, 1, kAnySize); representation_360p->AddNewSegment(10, 1, kAnySize);
representation_360p->AddNewSegment(11, 19, kAnySize); representation_360p->AddNewSegment(11, 19, kAnySize);