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:
parent
e5b6096857
commit
bb8cf87617
|
@ -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,10 +846,7 @@ 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
|
|
||||||
// keeps the timestamps around. It shouldn't out-of-memory for VOD.
|
|
||||||
void AdaptationSet::CheckSegmentAlignment(uint32_t representation_id,
|
|
||||||
uint64_t start_time,
|
uint64_t start_time,
|
||||||
uint64_t /* duration */) {
|
uint64_t /* duration */) {
|
||||||
if (segments_aligned_ == kSegmentAlignmentFalse ||
|
if (segments_aligned_ == kSegmentAlignmentFalse ||
|
||||||
|
@ -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,
|
||||||
|
|
|
@ -312,14 +312,19 @@ 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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue