[DASH] Fix possible incorrect segmentAlignment in static live profile

DASH live profile with static MPD was incorrectly handled in the same
way as live profile with dynamic MPD, i.e. by assuming the synchronized
Representations, which is incorrect.

It can be easily re-produced in multiple period outputs, i.e. with Ad
cues. It may happen with regular contents as well, due to race
condition, though with a much lower chance of occurring.

Fixes #435.
Bug: 111359775.

Change-Id: I7de087f5dd8602b4c4e35cb697d589fa3699e8a5
This commit is contained in:
KongQun Yang 2018-07-16 10:26:16 -07:00
parent 9a26ec1e83
commit 335a659fab
3 changed files with 105 additions and 45 deletions

View File

@ -274,8 +274,8 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
// Note: must be checked before checking segments_aligned_ (below). So that // Note: must be checked before checking segments_aligned_ (below). So that
// segments_aligned_ is set before checking below. // segments_aligned_ is set before checking below.
if (mpd_options_.dash_profile == DashProfile::kOnDemand) { if (mpd_options_.mpd_type == MpdType::kStatic) {
CheckVodSegmentAlignment(); CheckStaticSegmentAlignment();
} }
if (segments_aligned_ == kSegmentAlignmentTrue) { if (segments_aligned_ == kSegmentAlignmentTrue) {
@ -348,18 +348,17 @@ void AdaptationSet::AddAdaptationSetSwitching(
switchable_adaptation_sets_.push_back(adaptation_set); switchable_adaptation_sets_.push_back(adaptation_set);
} }
// Check segmentAlignment for Live here. Storing all start_time and duration // For dynamic MPD, storing all start_time and duration will out-of-memory
// will out-of-memory because there's no way of knowing when it will end. // because there's no way of knowing when it will end. Static MPD
// VOD subsegmentAlignment check is *not* done here because it is possible // subsegmentAlignment check is *not* done here because it is possible that some
// that some Representations might not have been added yet (e.g. a thread is // Representations might not have been added yet (e.g. a thread is assigned per
// assigned per muxer so one might run faster than others). // muxer so one might run faster than others). To be clear, for dynamic MPD, all
// To be clear, for Live, all Representations should be added before a // Representations should be added before a segment is added.
// 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) {
if (mpd_options_.dash_profile == DashProfile::kLive) { if (mpd_options_.mpd_type == MpdType::kDynamic) {
CheckLiveSegmentAlignment(representation_id, start_time, duration); CheckDynamicSegmentAlignment(representation_id, start_time, duration);
} else { } else {
representation_segment_start_times_[representation_id].push_back( representation_segment_start_times_[representation_id].push_back(
start_time); start_time);
@ -442,7 +441,7 @@ void AdaptationSet::UpdateFromMediaInfo(const MediaInfo& media_info) {
// 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.
void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id, void AdaptationSet::CheckDynamicSegmentAlignment(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 ||
@ -450,26 +449,29 @@ void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id,
return; return;
} }
std::list<uint64_t>& representation_start_times = std::list<uint64_t>& current_representation_start_times =
representation_segment_start_times_[representation_id]; representation_segment_start_times_[representation_id];
representation_start_times.push_back(start_time); current_representation_start_times.push_back(start_time);
// There's no way to detemine whether the segments are aligned if some // There's no way to detemine whether the segments are aligned if some
// representations do not have any segments. // representations do not have any segments.
if (representation_segment_start_times_.size() != representation_map_.size()) if (representation_segment_start_times_.size() != representation_map_.size())
return; return;
DCHECK(!representation_start_times.empty()); DCHECK(!current_representation_start_times.empty());
const uint64_t expected_start_time = representation_start_times.front(); const uint64_t expected_start_time =
for (RepresentationTimeline::const_iterator it = current_representation_start_times.front();
representation_segment_start_times_.begin(); for (const auto& key_value : representation_segment_start_times_) {
it != representation_segment_start_times_.end(); ++it) { const std::list<uint64_t>& representation_start_time = key_value.second;
// If there are no entries in a list, then there is no way for the // If there are no entries in a list, then there is no way for the
// segment alignment status to change. // segment alignment status to change.
// Note that it can be empty because entries get deleted below. // Note that it can be empty because entries get deleted below.
if (it->second.empty()) if (representation_start_time.empty())
return; return;
if (expected_start_time != it->second.front()) { if (expected_start_time != representation_start_time.front()) {
VLOG(1) << "Seeing Misaligned segments with different start_times: "
<< expected_start_time << " vs "
<< representation_start_time.front();
// Flag as false and clear the start times data, no need to keep it // Flag as false and clear the start times data, no need to keep it
// around. // around.
segments_aligned_ = kSegmentAlignmentFalse; segments_aligned_ = kSegmentAlignmentFalse;
@ -479,16 +481,15 @@ void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id,
} }
segments_aligned_ = kSegmentAlignmentTrue; segments_aligned_ = kSegmentAlignmentTrue;
for (RepresentationTimeline::iterator it = for (auto& key_value : representation_segment_start_times_) {
representation_segment_start_times_.begin(); std::list<uint64_t>& representation_start_time = key_value.second;
it != representation_segment_start_times_.end(); ++it) { representation_start_time.pop_front();
it->second.pop_front();
} }
} }
// Make sure all segements start times match for all Representations. // Make sure all segements start times match for all Representations.
// This assumes that the segments are contiguous. // This assumes that the segments are contiguous.
void AdaptationSet::CheckVodSegmentAlignment() { void AdaptationSet::CheckStaticSegmentAlignment() {
if (segments_aligned_ == kSegmentAlignmentFalse || if (segments_aligned_ == kSegmentAlignmentFalse ||
force_set_segment_alignment_) { force_set_segment_alignment_) {
return; return;

View File

@ -213,18 +213,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. /// This is only for dynamic MPD. For static MPD,
/// CheckStaticSegmentAlignment() 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 CheckLiveSegmentAlignment(uint32_t representation_id, void CheckDynamicSegmentAlignment(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_. // Checks representation_segment_start_times_ and sets segments_aligned_.
// Use this for VOD, do not use for Live. // Use this for static MPD, do not use for dynamic MPD.
void CheckVodSegmentAlignment(); void CheckStaticSegmentAlignment();
// 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);
@ -279,12 +280,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 // For static MPD, this will not be cleared, all the segment start times are
// stored in this. This should not out-of-memory for a reasonable length // stored in this. This should not out-of-memory for a reasonable length
// video and reasonable subsegment length. // video and reasonable subsegment length.
// For Live, the entries are deleted (see CheckLiveSegmentAlignment() // For dynamic MPD, the entries are deleted (see
// implementation comment) because storing the entire timeline is not // CheckDynamicSegmentAlignment() implementation comment) because storing the
// reasonable and may cause an out-of-memory problem. // entire timeline is not reasonable and may cause an out-of-memory problem.
RepresentationTimeline representation_segment_start_times_; RepresentationTimeline representation_segment_start_times_;
// Record the original AdaptationSets the trick play stream belongs to. There // Record the original AdaptationSets the trick play stream belongs to. There

View File

@ -743,8 +743,12 @@ TEST_F(OnDemandAdaptationSetTest, ForceSetsubsegmentAlignment) {
} }
// Verify that segmentAlignment is set to true if all the Representations // Verify that segmentAlignment is set to true if all the Representations
// segments' are aligned and the DASH profile is Live. // segments' are aligned and the DASH profile is Live and MPD type is dynamic.
TEST_F(LiveAdaptationSetTest, SegmentAlignment) { TEST_F(LiveAdaptationSetTest, SegmentAlignmentDynamicMpd) {
const uint64_t kStartTime = 0u;
const uint64_t kDuration = 10u;
const uint64_t kAnySize = 19834u;
const char k480pMediaInfo[] = const char k480pMediaInfo[] =
"video_info {\n" "video_info {\n"
" codec: 'avc1'\n" " codec: 'avc1'\n"
@ -767,17 +771,17 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignment) {
" pixel_height: 1\n" " pixel_height: 1\n"
"}\n" "}\n"
"container_type: 1\n"; "container_type: 1\n";
mpd_options_.mpd_type = MpdType::kDynamic;
// For dynamic MPD, we expect the Reprensentations to be synchronized, so the
// Reprensentations are added to AdaptationSet before any segments are added.
auto adaptation_set = CreateAdaptationSet(kNoLanguage); auto adaptation_set = CreateAdaptationSet(kNoLanguage);
Representation* representation_480p = Representation* representation_480p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
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_480p->AddNewSegment(kStartTime, kDuration, kAnySize);
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml()); xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml());
@ -792,6 +796,60 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignment) {
EXPECT_THAT(unaligned.get(), Not(AttributeSet("segmentAlignment"))); EXPECT_THAT(unaligned.get(), Not(AttributeSet("segmentAlignment")));
} }
// Verify that segmentAlignment is set to true if all the Representations
// segments' are aligned and the DASH profile is Live and MPD type is static.
TEST_F(LiveAdaptationSetTest, SegmentAlignmentStaticMpd) {
const uint64_t kStartTime = 0u;
const uint64_t kDuration = 10u;
const uint64_t kAnySize = 19834u;
const char k480pMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 8\n"
" pixel_height: 9\n"
"}\n"
"container_type: 1\n";
const char k360pMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 640\n"
" height: 360\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
mpd_options_.mpd_type = MpdType::kStatic;
auto adaptation_set = CreateAdaptationSet(kNoLanguage);
// For static MPD, the Representations are not synchronized, so it is possible
// that the second Representation is added after adding segments to the first
// Representation.
Representation* representation_480p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize);
Representation* representation_360p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
representation_480p->AddNewSegment(kStartTime + kDuration, kDuration,
kAnySize);
representation_360p->AddNewSegment(kStartTime + kDuration, kDuration,
kAnySize);
xml::scoped_xml_ptr<xmlNode> aligned(adaptation_set->GetXml());
EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true"));
}
// Verify that the width and height attribute are set if all the video // Verify that the width and height attribute are set if all the video
// representations have the same width and height. // representations have the same width and height.
TEST_F(OnDemandAdaptationSetTest, AdapatationSetWidthAndHeight) { TEST_F(OnDemandAdaptationSetTest, AdapatationSetWidthAndHeight) {