diff --git a/docs/source/options/dash_options.rst b/docs/source/options/dash_options.rst
index a36787aefd..ee48eaad43 100644
--- a/docs/source/options/dash_options.rst
+++ b/docs/source/options/dash_options.rst
@@ -48,3 +48,17 @@ DASH options
Any audio/text tracks tagged with this language will have
in the manifest. This allows the player to
choose the correct default language for the content.
+
+--allow_approximate_segment_timeline
+
+ For live profile only.
+
+ If enabled, segments with close duration (i.e. with difference less than
+ one sample) are considered to have the same duration. This enables
+ MPD generator to generate less SegmentTimeline entries. If all segments
+ are of the same duration except the last one, we will do further
+ optimization to use SegmentTemplate@duration instead and omit
+ SegmentTimeline completely.
+
+ Ignored if $Time$ is used in segment template, since $Time$ requires
+ accurate Segment Timeline.
diff --git a/packager/app/mpd_flags.cc b/packager/app/mpd_flags.cc
index 01a260d217..9d51435df9 100644
--- a/packager/app/mpd_flags.cc
+++ b/packager/app/mpd_flags.cc
@@ -51,3 +51,15 @@ DEFINE_bool(generate_dash_if_iop_compliant_mpd,
true,
"Try to generate DASH-IF IOP compliant MPD. This is best effort "
"and does not guarantee compliance.");
+DEFINE_bool(
+ allow_approximate_segment_timeline,
+ false,
+ "For live profile only. "
+ "If enabled, segments with close duration (i.e. with difference less than "
+ "one sample) are considered to have the same duration. This enables MPD "
+ "generator to generate less SegmentTimeline entries. If all segments are "
+ "of the same duration except the last one, we will do further optimization "
+ "to use SegmentTemplate@duration instead and omit SegmentTimeline "
+ "completely."
+ "Ignored if $Time$ is used in segment template, since $Time$ requires "
+ "accurate Segment Timeline.");
diff --git a/packager/app/mpd_flags.h b/packager/app/mpd_flags.h
index 8377df47ab..5675f65d6c 100644
--- a/packager/app/mpd_flags.h
+++ b/packager/app/mpd_flags.h
@@ -20,5 +20,6 @@ DECLARE_double(min_buffer_time);
DECLARE_double(suggested_presentation_delay);
DECLARE_string(utc_timings);
DECLARE_bool(generate_dash_if_iop_compliant_mpd);
+DECLARE_bool(allow_approximate_segment_timeline);
#endif // APP_MPD_FLAGS_H_
diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc
index 9a12916cc1..ce762dfa51 100644
--- a/packager/app/packager_main.cc
+++ b/packager/app/packager_main.cc
@@ -420,6 +420,8 @@ base::Optional GetPackagingParams() {
mpd_params.generate_static_live_mpd = FLAGS_generate_static_mpd;
mpd_params.generate_dash_if_iop_compliant_mpd =
FLAGS_generate_dash_if_iop_compliant_mpd;
+ mpd_params.allow_approximate_segment_timeline =
+ FLAGS_allow_approximate_segment_timeline;
HlsParams& hls_params = packaging_params.hls_params;
if (!GetHlsPlaylistType(FLAGS_hls_playlist_type, &hls_params.playlist_type)) {
diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc
index cf67e3ccb4..b40239b8ce 100644
--- a/packager/app/packager_util.cc
+++ b/packager/app/packager_util.cc
@@ -181,7 +181,9 @@ std::unique_ptr CreateDecryptionKeySource(
return decryption_key_source;
}
-MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params) {
+MpdOptions GetMpdOptions(bool on_demand_profile,
+ const MpdParams& mpd_params,
+ double target_segment_duration) {
MpdOptions mpd_options;
mpd_options.dash_profile =
on_demand_profile ? DashProfile::kOnDemand : DashProfile::kLive;
@@ -190,6 +192,7 @@ MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params) {
? MpdType::kStatic
: MpdType::kDynamic;
mpd_options.mpd_params = mpd_params;
+ mpd_options.target_segment_duration = target_segment_duration;
return mpd_options;
}
diff --git a/packager/app/packager_util.h b/packager/app/packager_util.h
index c5351545de..c47c624db0 100644
--- a/packager/app/packager_util.h
+++ b/packager/app/packager_util.h
@@ -45,7 +45,9 @@ std::unique_ptr CreateDecryptionKeySource(
const DecryptionParams& decryption_params);
/// @return MpdOptions from provided inputs.
-MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params);
+MpdOptions GetMpdOptions(bool on_demand_profile,
+ const MpdParams& mpd_params,
+ double target_segment_duration);
} // namespace media
} // namespace shaka
diff --git a/packager/mpd/base/media_info.proto b/packager/mpd/base/media_info.proto
index 721f7ee377..6555306b94 100644
--- a/packager/mpd/base/media_info.proto
+++ b/packager/mpd/base/media_info.proto
@@ -151,7 +151,7 @@ message MediaInfo {
// This value is the user input “segment duration”.
// This value is not necessarily the same as the value passed to
// MpdNotifier::NotifyNewSegment().
- optional float segment_duration_seconds = 12;
+ optional float segment_duration_seconds = 12 [deprecated = true];
// END LIVE only.
// URL fields for the corresponding file_name fields above.
diff --git a/packager/mpd/base/mpd_options.h b/packager/mpd/base/mpd_options.h
index dc261d9718..f44dc83f1e 100644
--- a/packager/mpd/base/mpd_options.h
+++ b/packager/mpd/base/mpd_options.h
@@ -26,6 +26,11 @@ struct MpdOptions {
DashProfile dash_profile = DashProfile::kOnDemand;
MpdType mpd_type = MpdType::kStatic;
MpdParams mpd_params;
+ /// This is the target segment duration requested by the user. The actual
+ /// segment duration may be different to the target segment duration.
+ /// This parameter is included here to calculate the approximate
+ /// SegmentTimeline if it is enabled.
+ double target_segment_duration = 0;
};
} // namespace shaka
diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc
index 49d51556ad..08fc5dc095 100644
--- a/packager/mpd/base/mpd_utils.cc
+++ b/packager/mpd/base/mpd_utils.cc
@@ -58,8 +58,7 @@ bool HasVODOnlyFields(const MediaInfo& media_info) {
bool HasLiveOnlyFields(const MediaInfo& media_info) {
return media_info.has_init_segment_url() ||
- media_info.has_segment_template_url() ||
- media_info.has_segment_duration_seconds();
+ media_info.has_segment_template_url();
}
void RemoveDuplicateAttributes(
diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc
index 716f1667bb..ebee94b55c 100644
--- a/packager/mpd/base/representation.cc
+++ b/packager/mpd/base/representation.cc
@@ -8,6 +8,8 @@
#include
+#include
+
#include "packager/base/logging.h"
#include "packager/file/file.h"
#include "packager/media/base/muxer_util.h"
@@ -115,9 +117,12 @@ Representation::Representation(
id_(id),
bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks),
mpd_options_(mpd_options),
- start_number_(1),
state_change_listener_(std::move(state_change_listener)),
- output_suppression_flags_(0) {}
+ allow_approximate_segment_timeline_(
+ // TODO(kqyang): Need a better check. $Time is legitimate but not a
+ // template.
+ media_info.segment_template().find("$Time") == std::string::npos &&
+ mpd_options_.mpd_params.allow_approximate_segment_timeline) {}
Representation::Representation(
const Representation& representation,
@@ -200,12 +205,8 @@ void Representation::AddNewSegment(uint64_t start_time,
if (state_change_listener_)
state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
- if (IsContiguous(start_time, duration, size)) {
- ++segment_infos_.back().repeat;
- } else {
- SegmentInfo s = {start_time, duration, /* Not repeat. */ 0};
- segment_infos_.push_back(s);
- }
+
+ AddSegmentInfo(start_time, duration);
bandwidth_estimator_.AddBlock(
size, static_cast(duration) / media_info_.reference_time_scale());
@@ -214,12 +215,17 @@ void Representation::AddNewSegment(uint64_t start_time,
DCHECK_GE(segment_infos_.size(), 1u);
}
-void Representation::SetSampleDuration(uint32_t sample_duration) {
+void Representation::SetSampleDuration(uint32_t frame_duration) {
+ // Sample duration is used to generate approximate SegmentTimeline.
+ // Text is required to have exactly the same segment duration.
+ if (media_info_.has_audio_info() || media_info_.has_video_info())
+ frame_duration_ = frame_duration;
+
if (media_info_.has_video_info()) {
- media_info_.mutable_video_info()->set_frame_duration(sample_duration);
+ media_info_.mutable_video_info()->set_frame_duration(frame_duration);
if (state_change_listener_) {
state_change_listener_->OnSetFrameRateForRepresentation(
- sample_duration, media_info_.video_info().time_scale());
+ frame_duration, media_info_.video_info().time_scale());
}
}
}
@@ -344,55 +350,87 @@ bool Representation::HasRequiredMediaInfoFields() const {
return true;
}
-bool Representation::IsContiguous(uint64_t start_time,
- uint64_t duration,
- uint64_t size) const {
- if (segment_infos_.empty())
- return false;
+void Representation::AddSegmentInfo(uint64_t start_time, uint64_t duration) {
+ const uint64_t kNoRepeat = 0;
+ const uint64_t adjusted_duration = AdjustDuration(duration);
- // Contiguous segment.
- const SegmentInfo& previous = segment_infos_.back();
- const uint64_t previous_segment_end_time =
- previous.start_time + previous.duration * (previous.repeat + 1);
- if (previous_segment_end_time == start_time &&
- segment_infos_.back().duration == duration) {
- return true;
+ if (!segment_infos_.empty()) {
+ // Contiguous segment.
+ const SegmentInfo& previous = segment_infos_.back();
+ const uint64_t previous_segment_end_time =
+ previous.start_time + previous.duration * (previous.repeat + 1);
+ // Make it continuous if the segment start time is close to previous segment
+ // end time.
+ if (ApproximiatelyEqual(previous_segment_end_time, start_time)) {
+ const uint64_t segment_end_time_for_same_duration =
+ previous_segment_end_time + previous.duration;
+ const uint64_t actual_segment_end_time = start_time + duration;
+ // Consider the segments having identical duration if the segment end time
+ // is close to calculated segment end time by assuming identical duration.
+ if (ApproximiatelyEqual(segment_end_time_for_same_duration,
+ actual_segment_end_time)) {
+ ++segment_infos_.back().repeat;
+ } else {
+ segment_infos_.push_back(
+ {previous_segment_end_time,
+ actual_segment_end_time - previous_segment_end_time, kNoRepeat});
+ }
+ return;
+ }
+
+ // A gap since previous.
+ const uint64_t kRoundingErrorGrace = 5;
+ if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
+ LOG(WARNING) << "Found a gap of size "
+ << (start_time - previous_segment_end_time)
+ << " > kRoundingErrorGrace (" << kRoundingErrorGrace
+ << "). The new segment starts at " << start_time
+ << " but the previous segment ends at "
+ << previous_segment_end_time << ".";
+ }
+
+ // No overlapping segments.
+ if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
+ LOG(WARNING)
+ << "Segments should not be overlapping. The new segment starts at "
+ << start_time << " but the previous segment ends at "
+ << previous_segment_end_time << ".";
+ }
}
- // No out of order segments.
- const uint64_t previous_segment_start_time =
- previous.start_time + previous.duration * previous.repeat;
- if (previous_segment_start_time >= start_time) {
- LOG(ERROR) << "Segments should not be out of order segment. Adding segment "
- "with start_time == "
- << start_time << " but the previous segment starts at "
- << previous_segment_start_time << ".";
- return false;
- }
+ segment_infos_.push_back({start_time, adjusted_duration, kNoRepeat});
+}
- // A gap since previous.
- const uint64_t kRoundingErrorGrace = 5;
- if (previous_segment_end_time + kRoundingErrorGrace < start_time) {
- LOG(WARNING) << "Found a gap of size "
- << (start_time - previous_segment_end_time)
- << " > kRoundingErrorGrace (" << kRoundingErrorGrace
- << "). The new segment starts at " << start_time
- << " but the previous segment ends at "
- << previous_segment_end_time << ".";
- return false;
- }
+bool Representation::ApproximiatelyEqual(uint64_t time1, uint64_t time2) const {
+ if (!allow_approximate_segment_timeline_)
+ return time1 == time2;
- // No overlapping segments.
- if (start_time < previous_segment_end_time - kRoundingErrorGrace) {
- LOG(WARNING)
- << "Segments should not be overlapping. The new segment starts at "
- << start_time << " but the previous segment ends at "
- << previous_segment_end_time << ".";
- return false;
- }
+ // It is not always possible to align segment duration to target duration
+ // exactly. For example, for AAC with sampling rate of 44100, there are always
+ // 1024 audio samples per frame, so the frame duration is 1024/44100. For a
+ // target duration of 2 seconds, the closest segment duration would be 1.984
+ // or 2.00533.
- // Within rounding error grace but technically not contiguous in terms of MPD.
- return false;
+ // An arbitrary error threshold cap. This makes sure that the error is not too
+ // large for large samples.
+ const double kErrorThresholdSeconds = 0.05;
+
+ // So we consider two times equal if they differ by less than one sample.
+ const uint32_t error_threshold =
+ std::min(frame_duration_,
+ static_cast(kErrorThresholdSeconds *
+ media_info_.reference_time_scale()));
+ return time1 < time2 + error_threshold && time2 < time1 + error_threshold;
+}
+
+uint64_t Representation::AdjustDuration(uint64_t duration) const {
+ if (!allow_approximate_segment_timeline_)
+ return duration;
+ const uint64_t scaled_target_duration =
+ mpd_options_.target_segment_duration * media_info_.reference_time_scale();
+ return ApproximiatelyEqual(scaled_target_duration, duration)
+ ? scaled_target_duration
+ : duration;
}
void Representation::SlideWindow() {
diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h
index 6b8899b0d7..0cdd8f7a75 100644
--- a/packager/mpd/base/representation.h
+++ b/packager/mpd/base/representation.h
@@ -178,11 +178,18 @@ class Representation {
// Representation. Otherwise returns false.
bool HasRequiredMediaInfoFields() const;
- // Return false if the segment should be considered a new segment. True if the
- // segment is contiguous.
- bool IsContiguous(uint64_t start_time,
- uint64_t duration,
- uint64_t size) const;
+ // Add a SegmentInfo. This function may insert an adjusted SegmentInfo if
+ // |allow_approximate_segment_timeline_| is set.
+ void AddSegmentInfo(uint64_t start_time, uint64_t duration);
+
+ // Check if two timestamps are approximately equal if
+ // |allow_approximate_segment_timeline_| is set; Otherwise check whether the
+ // two times match.
+ bool ApproximiatelyEqual(uint64_t time1, uint64_t time2) const;
+
+ // Return adjusted duration if |allow_aproximate_segment_timeline_or_duration|
+ // is set; otherwise duration is returned without adjustment.
+ uint64_t AdjustDuration(uint64_t duration) const;
// Remove elements from |segment_infos_| for dynamic live profile. Increments
// |start_number_| by the number of segments removed.
@@ -216,14 +223,21 @@ class Representation {
// startNumber attribute for SegmentTemplate.
// Starts from 1.
- uint32_t start_number_;
+ uint32_t start_number_ = 1;
// If this is not null, then Representation is responsible for calling the
// right methods at right timings.
std::unique_ptr state_change_listener_;
// Bit vector for tracking witch attributes should not be output.
- int output_suppression_flags_;
+ int output_suppression_flags_ = 0;
+
+ // When set to true, allows segments to have slightly different durations (up
+ // to one sample).
+ const bool allow_approximate_segment_timeline_ = false;
+ // Segments with duration difference less than one frame duration are
+ // considered to have the same duration.
+ uint32_t frame_duration_ = 0;
};
} // namespace shaka
diff --git a/packager/mpd/base/representation_unittest.cc b/packager/mpd/base/representation_unittest.cc
index ca1a2d47c9..16b5cb0785 100644
--- a/packager/mpd/base/representation_unittest.cc
+++ b/packager/mpd/base/representation_unittest.cc
@@ -17,7 +17,10 @@
#include "packager/mpd/test/mpd_builder_test_helper.h"
#include "packager/mpd/test/xml_compare.h"
+using ::testing::Bool;
using ::testing::Not;
+using ::testing::Values;
+using ::testing::WithParamInterface;
namespace shaka {
namespace {
@@ -395,6 +398,8 @@ const char kSElementTemplateWithoutR[] =
"\n";
const int kDefaultStartNumber = 1;
const uint32_t kDefaultTimeScale = 1000u;
+const uint64_t kScaledTargetSegmentDuration = 10;
+const uint32_t kSampleDuration = 2;
std::string GetDefaultMediaInfo() {
const char kMediaInfo[] =
@@ -487,18 +492,8 @@ TEST_F(SegmentTemplateTest, OneSegmentNormal) {
const uint64_t kSize = 128;
AddSegments(kStartTime, kDuration, kSize, 0);
- const char kExpectedXml[] =
- "\n"
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- "\n";
- EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml));
+ expected_s_elements_ = "";
+ EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
}
TEST_F(SegmentTemplateTest, RepresentationClone) {
@@ -521,7 +516,6 @@ TEST_F(SegmentTemplateTest, RepresentationClone) {
" width=\"720\" height=\"480\" frameRate=\"10/5\">\n"
" \n"
- " \n"
" \n"
"\n";
EXPECT_THAT(cloned_representation->GetXml().get(),
@@ -545,7 +539,7 @@ TEST_F(SegmentTemplateTest, PresentationTimeOffset) {
" initialization=\"init.mp4\" media=\"$Time$.mp4\" startNumber=\"1\">\n"
" \n"
" \n"
- " \n"
+ " \n"
" \n"
"\n";
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml));
@@ -686,7 +680,7 @@ TEST_F(SegmentTemplateTest, OverlappingSegmentsWithinErrorRange) {
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
}
-class TimeShiftBufferDepthTest : public SegmentTemplateTest {
+class SegmentTimelineTestBase : public SegmentTemplateTest {
public:
void SetUp() override {
// The only diff with current GetDefaultMediaInfo() is that this uses
@@ -708,6 +702,8 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest {
const std::string& number_template_media_info =
base::StringPrintf(kMediaInfo, kDefaultTimeScale);
mpd_options_.mpd_type = MpdType::kDynamic;
+ mpd_options_.target_segment_duration =
+ static_cast(kScaledTargetSegmentDuration) / kDefaultTimeScale;
representation_ =
CreateRepresentation(ConvertToMediaInfo(number_template_media_info),
kAnyRepresentationId, NoListener());
@@ -734,17 +730,198 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest {
expected_start_number,
expected_s_element.c_str());
}
+};
+
+class ApproximateSegmentTimelineTest : public SegmentTimelineTestBase,
+ public WithParamInterface {
+ public:
+ void SetUp() override {
+ allow_approximate_segment_timeline_ = GetParam();
+ mpd_options_.mpd_params.allow_approximate_segment_timeline =
+ allow_approximate_segment_timeline_;
+ SegmentTimelineTestBase::SetUp();
+ representation_->SetSampleDuration(kSampleDuration);
+ }
+
+ std::string ExpectedXml(const std::string& expected_s_element) {
+ return SegmentTimelineTestBase::ExpectedXml(expected_s_element,
+ kDefaultStartNumber);
+ }
+
+ protected:
+ bool allow_approximate_segment_timeline_;
+};
+
+TEST_P(ApproximateSegmentTimelineTest, SegmentDurationAdjusted) {
+ const uint64_t kStartTime = 0;
+ const uint64_t kDurationSmaller =
+ kScaledTargetSegmentDuration - kSampleDuration / 2;
+ const uint64_t kSize = 128;
+ AddSegments(kStartTime, kDurationSmaller, kSize, 0);
+
+ std::string expected_s_elements;
+ if (allow_approximate_segment_timeline_) {
+ expected_s_elements = base::StringPrintf(
+ kSElementTemplateWithoutR, kStartTime, kScaledTargetSegmentDuration);
+ } else {
+ expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR,
+ kStartTime, kDurationSmaller);
+ }
+ EXPECT_THAT(representation_->GetXml().get(),
+ XmlNodeEqual(ExpectedXml(expected_s_elements)));
+}
+
+TEST_P(ApproximateSegmentTimelineTest,
+ SegmentDurationAdjustedWithNonZeroStartTime) {
+ const uint64_t kStartTime = 12345;
+ const uint64_t kDurationSmaller =
+ kScaledTargetSegmentDuration - kSampleDuration / 2;
+ const uint64_t kSize = 128;
+
+ AddSegments(kStartTime, kDurationSmaller, kSize, 0);
+
+ std::string expected_s_elements;
+ if (allow_approximate_segment_timeline_) {
+ expected_s_elements = base::StringPrintf(
+ kSElementTemplateWithoutR, kStartTime, kScaledTargetSegmentDuration);
+ } else {
+ expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR,
+ kStartTime, kDurationSmaller);
+ }
+ EXPECT_THAT(representation_->GetXml().get(),
+ XmlNodeEqual(ExpectedXml(expected_s_elements)));
+}
+
+TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations) {
+ const uint64_t kStartTime = 0;
+ const uint64_t kDurationSmaller =
+ kScaledTargetSegmentDuration - kSampleDuration / 2;
+ const uint64_t kDurationLarger =
+ kScaledTargetSegmentDuration + kSampleDuration / 2;
+ const uint64_t kSize = 128;
+ AddSegments(kStartTime, kDurationSmaller, kSize, 0);
+ AddSegments(kStartTime + kDurationSmaller, kDurationLarger, kSize, 0);
+ AddSegments(kStartTime + kDurationSmaller + kDurationLarger, kDurationSmaller,
+ kSize, 0);
+
+ std::string expected_s_elements;
+ if (allow_approximate_segment_timeline_) {
+ uint64_t kNumSegments = 3;
+ expected_s_elements =
+ base::StringPrintf(kSElementTemplate, kStartTime,
+ kScaledTargetSegmentDuration, kNumSegments - 1);
+ } else {
+ expected_s_elements =
+ base::StringPrintf(kSElementTemplateWithoutR, kStartTime,
+ kDurationSmaller) +
+ base::StringPrintf(kSElementTemplateWithoutR,
+ kStartTime + kDurationSmaller, kDurationLarger) +
+ base::StringPrintf(kSElementTemplateWithoutR,
+ kStartTime + kDurationSmaller + kDurationLarger,
+ kDurationSmaller);
+ }
+ EXPECT_THAT(representation_->GetXml().get(),
+ XmlNodeEqual(ExpectedXml(expected_s_elements)));
+}
+
+// We assume the actual segment duration fluctuates around target segment
+// duration; if it is not the case (which should not happen with our demuxer),
+// this is how the output would look like.
+TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations2) {
+ const uint64_t kStartTime = 0;
+ const uint64_t kDurationLarger =
+ kScaledTargetSegmentDuration + kSampleDuration / 2;
+ const uint64_t kSize = 128;
+ AddSegments(kStartTime, kDurationLarger, kSize, 0);
+ AddSegments(kStartTime + kDurationLarger, kDurationLarger, kSize, 0);
+ AddSegments(kStartTime + 2 * kDurationLarger, kDurationLarger, kSize, 0);
+
+ std::string expected_s_elements;
+ if (allow_approximate_segment_timeline_) {
+ expected_s_elements =
+ ""
+ "";
+ } else {
+ uint64_t kNumSegments = 3;
+ expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime,
+ kDurationLarger, kNumSegments - 1);
+ }
+ EXPECT_THAT(representation_->GetXml().get(),
+ XmlNodeEqual(ExpectedXml(expected_s_elements)));
+}
+
+TEST_P(ApproximateSegmentTimelineTest, FillSmallGap) {
+ const uint64_t kStartTime = 0;
+ const uint64_t kDuration = kScaledTargetSegmentDuration;
+ const uint64_t kGap = kSampleDuration / 2;
+ const uint64_t kSize = 128;
+ AddSegments(kStartTime, kDuration, kSize, 0);
+ AddSegments(kStartTime + kDuration + kGap, kDuration, kSize, 0);
+ AddSegments(kStartTime + 2 * kDuration + kGap, kDuration, kSize, 0);
+
+ std::string expected_s_elements;
+ if (allow_approximate_segment_timeline_) {
+ uint64_t kNumSegments = 3;
+ expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime,
+ kDuration, kNumSegments - 1);
+ } else {
+ expected_s_elements =
+ base::StringPrintf(kSElementTemplateWithoutR, kStartTime, kDuration) +
+ base::StringPrintf(kSElementTemplate, kStartTime + kDuration + kGap,
+ kDuration, static_cast(1) /* repeat */);
+ }
+ EXPECT_THAT(representation_->GetXml().get(),
+ XmlNodeEqual(ExpectedXml(expected_s_elements)));
+}
+
+TEST_P(ApproximateSegmentTimelineTest, FillSmallOverlap) {
+ const uint64_t kStartTime = 0;
+ const uint64_t kDuration = kScaledTargetSegmentDuration;
+ const uint64_t kOverlap = kSampleDuration / 2;
+ const uint64_t kSize = 128;
+ AddSegments(kStartTime, kDuration, kSize, 0);
+ AddSegments(kStartTime + kDuration - kOverlap, kDuration, kSize, 0);
+ AddSegments(kStartTime + 2 * kDuration - kOverlap, kDuration, kSize, 0);
+
+ std::string expected_s_elements;
+ if (allow_approximate_segment_timeline_) {
+ uint64_t kNumSegments = 3;
+ expected_s_elements = base::StringPrintf(kSElementTemplate, kStartTime,
+ kDuration, kNumSegments - 1);
+ } else {
+ expected_s_elements =
+ base::StringPrintf(kSElementTemplateWithoutR, kStartTime, kDuration) +
+ base::StringPrintf(kSElementTemplate, kStartTime + kDuration - kOverlap,
+ kDuration, static_cast(1) /* repeat */);
+ }
+ EXPECT_THAT(representation_->GetXml().get(),
+ XmlNodeEqual(ExpectedXml(expected_s_elements)));
+}
+
+INSTANTIATE_TEST_CASE_P(ApproximateSegmentTimelineTest,
+ ApproximateSegmentTimelineTest,
+ Bool());
+
+class TimeShiftBufferDepthTest : public SegmentTimelineTestBase,
+ public WithParamInterface {
+ public:
+ void SetUp() override {
+ initial_start_time_ = GetParam();
+ SegmentTimelineTestBase::SetUp();
+ }
MpdOptions* mutable_mpd_options() { return &mpd_options_; }
+
+ protected:
+ uint64_t initial_start_time_;
};
// All segments have the same duration and size.
-TEST_F(TimeShiftBufferDepthTest, Normal) {
+TEST_P(TimeShiftBufferDepthTest, Normal) {
const int kTimeShiftBufferDepth = 10; // 10 sec.
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth;
- const uint64_t kInitialStartTime = 0;
// Trick to make every segment 1 second long.
const uint64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 10000;
@@ -753,7 +930,7 @@ TEST_F(TimeShiftBufferDepthTest, Normal) {
CHECK_EQ(kDuration / kDefaultTimeScale * kRepeat, kLength);
- AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
+ AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
// There should only be the last 11 segments because timeshift is 10 sec and
// each segment is 1 sec and the latest segments start time is "current
@@ -761,11 +938,12 @@ TEST_F(TimeShiftBufferDepthTest, Normal) {
// depth.
// Also note that S@r + 1 is the actual number of segments.
const int kExpectedRepeatsLeft = kTimeShiftBufferDepth;
- const std::string expected_s_element = base::StringPrintf(
- kSElementTemplate, kDuration * (kRepeat - kExpectedRepeatsLeft),
- kDuration, static_cast(kExpectedRepeatsLeft));
-
const int kExpectedStartNumber = kRepeat - kExpectedRepeatsLeft + 1;
+
+ const std::string expected_s_element = base::StringPrintf(
+ kSElementTemplate,
+ initial_start_time_ + kDuration * (kRepeat - kExpectedRepeatsLeft),
+ kDuration, static_cast(kExpectedRepeatsLeft));
EXPECT_THAT(
representation_->GetXml().get(),
XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber)));
@@ -776,42 +954,38 @@ TEST_F(TimeShiftBufferDepthTest, Normal) {
// For example if TimeShiftBufferDepth = 1 min. and a 10 min segment was just
// added. Before that 9 min segment was added. The 9 min segment should not be
// removed from the MPD.
-TEST_F(TimeShiftBufferDepthTest, TimeShiftBufferDepthShorterThanSegmentLength) {
+TEST_P(TimeShiftBufferDepthTest, TimeShiftBufferDepthShorterThanSegmentLength) {
const int kTimeShiftBufferDepth = 10; // 10 sec.
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth;
- const uint64_t kInitialStartTime = 0;
// Each duration is a second longer than timeShiftBufferDepth.
const uint64_t kDuration = kDefaultTimeScale * (kTimeShiftBufferDepth + 1);
const uint64_t kSize = 10000;
const uint64_t kRepeat = 1;
- AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
+ AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
- // The two segments should be both present.
const std::string expected_s_element = base::StringPrintf(
- kSElementTemplate, kInitialStartTime, kDuration, kRepeat);
-
+ kSElementTemplate, initial_start_time_, kDuration, kRepeat);
EXPECT_THAT(
representation_->GetXml().get(),
XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber)));
}
// More generic version the normal test.
-TEST_F(TimeShiftBufferDepthTest, Generic) {
+TEST_P(TimeShiftBufferDepthTest, Generic) {
const int kTimeShiftBufferDepth = 30;
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth;
- const uint64_t kInitialStartTime = 123;
const uint64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 10000;
const uint64_t kRepeat = 1000;
- AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
+ AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
const uint64_t first_s_element_end_time =
- kInitialStartTime + kDuration * (kRepeat + 1);
+ initial_start_time_ + kDuration * (kRepeat + 1);
// Now add 2 kTimeShiftBufferDepth long segments.
const int kNumMoreSegments = 2;
@@ -839,20 +1013,19 @@ TEST_F(TimeShiftBufferDepthTest, Generic) {
// This should have all of the two-second segments and 60 one-second
// segments. Note that it expects 60 segments from the first S element because
// the most recent segment added does not count
-TEST_F(TimeShiftBufferDepthTest, MoreThanOneS) {
+TEST_P(TimeShiftBufferDepthTest, MoreThanOneS) {
const int kTimeShiftBufferDepth = 100;
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth;
- const uint64_t kInitialStartTime = 0;
const uint64_t kSize = 20000;
const uint64_t kOneSecondDuration = kDefaultTimeScale;
const uint64_t kOneSecondSegmentRepeat = 99;
- AddSegments(kInitialStartTime, kOneSecondDuration, kSize,
+ AddSegments(initial_start_time_, kOneSecondDuration, kSize,
kOneSecondSegmentRepeat);
const uint64_t first_s_element_end_time =
- kInitialStartTime + kOneSecondDuration * (kOneSecondSegmentRepeat + 1);
+ initial_start_time_ + kOneSecondDuration * (kOneSecondSegmentRepeat + 1);
const uint64_t kTwoSecondDuration = 2 * kDefaultTimeScale;
const uint64_t kTwoSecondSegmentRepeat = 20;
@@ -864,7 +1037,8 @@ TEST_F(TimeShiftBufferDepthTest, MoreThanOneS) {
kTimeShiftBufferDepth;
std::string expected_s_element = base::StringPrintf(
- kSElementTemplate, kOneSecondDuration * kExpectedRemovedSegments,
+ kSElementTemplate,
+ initial_start_time_ + kOneSecondDuration * kExpectedRemovedSegments,
kOneSecondDuration, kOneSecondSegmentRepeat - kExpectedRemovedSegments);
expected_s_element +=
base::StringPrintf(kSElementTemplate, first_s_element_end_time,
@@ -884,20 +1058,19 @@ TEST_F(TimeShiftBufferDepthTest, MoreThanOneS) {
//
// and we add another contiguous 2 second segment.
// Then the first S element's last segment should still be in the MPD.
-TEST_F(TimeShiftBufferDepthTest, UseLastSegmentInS) {
+TEST_P(TimeShiftBufferDepthTest, UseLastSegmentInS) {
const int kTimeShiftBufferDepth = 9;
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth;
- const uint64_t kInitialStartTime = 1;
const uint64_t kDuration1 = static_cast(kDefaultTimeScale * 1.5);
const uint64_t kSize = 20000;
const uint64_t kRepeat1 = 1;
- AddSegments(kInitialStartTime, kDuration1, kSize, kRepeat1);
+ AddSegments(initial_start_time_, kDuration1, kSize, kRepeat1);
const uint64_t first_s_element_end_time =
- kInitialStartTime + kDuration1 * (kRepeat1 + 1);
+ initial_start_time_ + kDuration1 * (kRepeat1 + 1);
const uint64_t kTwoSecondDuration = 2 * kDefaultTimeScale;
const uint64_t kTwoSecondSegmentRepeat = 4;
@@ -907,7 +1080,7 @@ TEST_F(TimeShiftBufferDepthTest, UseLastSegmentInS) {
std::string expected_s_element = base::StringPrintf(
kSElementTemplateWithoutR,
- kInitialStartTime + kDuration1, // Expect one segment removed.
+ initial_start_time_ + kDuration1, // Expect one segment removed.
kDuration1);
expected_s_element +=
@@ -918,12 +1091,11 @@ TEST_F(TimeShiftBufferDepthTest, UseLastSegmentInS) {
}
// Gap between S elements but both should be included.
-TEST_F(TimeShiftBufferDepthTest, NormalGap) {
+TEST_P(TimeShiftBufferDepthTest, NormalGap) {
const int kTimeShiftBufferDepth = 10;
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth;
- const uint64_t kInitialStartTime = 0;
const uint64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 20000;
const uint64_t kRepeat = 6;
@@ -932,16 +1104,16 @@ TEST_F(TimeShiftBufferDepthTest, NormalGap) {
CHECK_LT(kRepeat - 1u, static_cast(kTimeShiftBufferDepth));
CHECK_EQ(kDuration, kDefaultTimeScale);
- AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
+ AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
const uint64_t first_s_element_end_time =
- kInitialStartTime + kDuration * (kRepeat + 1);
+ initial_start_time_ + kDuration * (kRepeat + 1);
const uint64_t gap_s_element_start_time = first_s_element_end_time + 1;
AddSegments(gap_s_element_start_time, kDuration, kSize, /* no repeat */ 0);
std::string expected_s_element = base::StringPrintf(
- kSElementTemplate, kInitialStartTime, kDuration, kRepeat);
+ kSElementTemplate, initial_start_time_, kDuration, kRepeat);
expected_s_element += base::StringPrintf(kSElementTemplateWithoutR,
gap_s_element_start_time, kDuration);
@@ -951,19 +1123,18 @@ TEST_F(TimeShiftBufferDepthTest, NormalGap) {
}
// Case where there is a huge gap so the first S element is removed.
-TEST_F(TimeShiftBufferDepthTest, HugeGap) {
+TEST_P(TimeShiftBufferDepthTest, HugeGap) {
const int kTimeShiftBufferDepth = 10;
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth;
- const uint64_t kInitialStartTime = 0;
const uint64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 20000;
const uint64_t kRepeat = 6;
- AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
+ AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
const uint64_t first_s_element_end_time =
- kInitialStartTime + kDuration * (kRepeat + 1);
+ initial_start_time_ + kDuration * (kRepeat + 1);
// Big enough gap so first S element should not be there.
const uint64_t gap_s_element_start_time =
@@ -987,34 +1158,34 @@ TEST_F(TimeShiftBufferDepthTest, HugeGap) {
}
// Check if startNumber is working correctly.
-TEST_F(TimeShiftBufferDepthTest, ManySegments) {
+TEST_P(TimeShiftBufferDepthTest, ManySegments) {
const int kTimeShiftBufferDepth = 1;
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
kTimeShiftBufferDepth;
- const uint64_t kInitialStartTime = 0;
const uint64_t kDuration = kDefaultTimeScale;
const uint64_t kSize = 20000;
const uint64_t kRepeat = 10000;
const uint64_t kTotalNumSegments = kRepeat + 1;
- AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
+ AddSegments(initial_start_time_, kDuration, kSize, kRepeat);
const int kExpectedSegmentsLeft = kTimeShiftBufferDepth + 1;
const int kExpectedSegmentsRepeat = kExpectedSegmentsLeft - 1;
const int kExpectedRemovedSegments =
kTotalNumSegments - kExpectedSegmentsLeft;
+ const int kExpectedStartNumber =
+ kDefaultStartNumber + kExpectedRemovedSegments;
std::string expected_s_element = base::StringPrintf(
- kSElementTemplate, kExpectedRemovedSegments * kDuration, kDuration,
+ kSElementTemplate,
+ initial_start_time_ + kExpectedRemovedSegments * kDuration, kDuration,
static_cast(kExpectedSegmentsRepeat));
-
EXPECT_THAT(
representation_->GetXml().get(),
- XmlNodeEqual(ExpectedXml(
- expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
+ XmlNodeEqual(ExpectedXml(expected_s_element, kExpectedStartNumber)));
}
-TEST_F(TimeShiftBufferDepthTest, DeleteSegmentsOutsideOfLiveWindow) {
+TEST_P(TimeShiftBufferDepthTest, DeleteSegmentsOutsideOfLiveWindow) {
const char kSegmentTemplate[] = "memory://$Number$.mp4";
const char kStringPrintTemplate[] = "memory://%d.mp4";
@@ -1092,4 +1263,8 @@ TEST_F(TimeShiftBufferDepthTest, DeleteSegmentsOutsideOfLiveWindow) {
kStringPrintTemplate, last_available_segment_index - 1)));
}
+INSTANTIATE_TEST_CASE_P(InitialStartTime,
+ TimeShiftBufferDepthTest,
+ Values(0, 1000));
+
} // namespace shaka
diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc
index 125ebbaaa3..058e13723d 100644
--- a/packager/mpd/base/xml/xml_node.cc
+++ b/packager/mpd/base/xml/xml_node.cc
@@ -6,6 +6,8 @@
#include "packager/mpd/base/xml/xml_node.h"
+#include
+
#include
#include
@@ -17,6 +19,11 @@
#include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/segment_info.h"
+DEFINE_bool(segment_template_constant_duration,
+ false,
+ "Generates SegmentTemplate@duration if all segments except the "
+ "last one has the same duration if this flag is set to true.");
+
namespace shaka {
using xml::XmlNode;
@@ -31,16 +38,42 @@ std::string RangeToString(const Range& range) {
base::Uint64ToString(range.end());
}
+// Check if segments are continuous and all segments except the last one are of
+// the same duration.
+bool IsTimelineConstantDuration(const std::list& segment_infos,
+ uint32_t start_number) {
+ if (!FLAGS_segment_template_constant_duration)
+ return false;
+
+ DCHECK(!segment_infos.empty());
+ if (segment_infos.size() > 2)
+ return false;
+
+ const SegmentInfo& first_segment = segment_infos.front();
+ if (first_segment.start_time != first_segment.duration * (start_number - 1))
+ return false;
+
+ if (segment_infos.size() == 1)
+ return true;
+
+ const SegmentInfo& last_segment = segment_infos.back();
+ if (last_segment.repeat != 0)
+ return false;
+
+ const uint64_t expected_last_segment_start_time =
+ first_segment.start_time +
+ first_segment.duration * (first_segment.repeat + 1);
+ return expected_last_segment_start_time == last_segment.start_time;
+}
+
bool PopulateSegmentTimeline(const std::list& segment_infos,
XmlNode* segment_timeline) {
- for (std::list::const_iterator it = segment_infos.begin();
- it != segment_infos.end();
- ++it) {
+ for (const SegmentInfo& segment_info : segment_infos) {
XmlNode s_element("S");
- s_element.SetIntegerAttribute("t", it->start_time);
- s_element.SetIntegerAttribute("d", it->duration);
- if (it->repeat > 0)
- s_element.SetIntegerAttribute("r", it->repeat);
+ s_element.SetIntegerAttribute("t", segment_info.start_time);
+ s_element.SetIntegerAttribute("d", segment_info.duration);
+ if (segment_info.repeat > 0)
+ s_element.SetIntegerAttribute("r", segment_info.repeat);
CHECK(segment_timeline->AddChild(s_element.PassScopedPtr()));
}
@@ -347,12 +380,21 @@ bool RepresentationXmlNode::AddLiveOnlyInfo(
segment_template.SetIntegerAttribute("startNumber", start_number);
}
- // TODO(rkuroiwa): Find out when a live MPD doesn't require SegmentTimeline.
- XmlNode segment_timeline("SegmentTimeline");
-
- return PopulateSegmentTimeline(segment_infos, &segment_timeline) &&
- segment_template.AddChild(segment_timeline.PassScopedPtr()) &&
- AddChild(segment_template.PassScopedPtr());
+ if (!segment_infos.empty()) {
+ // Don't use SegmentTimeline if all segments except the last one are of
+ // the same duration.
+ if (IsTimelineConstantDuration(segment_infos, start_number)) {
+ segment_template.SetIntegerAttribute("duration",
+ segment_infos.front().duration);
+ } else {
+ XmlNode segment_timeline("SegmentTimeline");
+ if (!PopulateSegmentTimeline(segment_infos, &segment_timeline) ||
+ !segment_template.AddChild(segment_timeline.PassScopedPtr())) {
+ return false;
+ }
+ }
+ }
+ return AddChild(segment_template.PassScopedPtr());
}
bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
diff --git a/packager/mpd/base/xml/xml_node_unittest.cc b/packager/mpd/base/xml/xml_node_unittest.cc
index 817df14907..2e164c9af5 100644
--- a/packager/mpd/base/xml/xml_node_unittest.cc
+++ b/packager/mpd/base/xml/xml_node_unittest.cc
@@ -4,6 +4,7 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
+#include
#include
#include
#include
@@ -16,6 +17,8 @@
#include "packager/mpd/base/xml/xml_node.h"
#include "packager/mpd/test/xml_compare.h"
+DECLARE_bool(segment_template_constant_duration);
+
namespace shaka {
namespace xml {
@@ -191,5 +194,173 @@ TEST(XmlNodeTest, AddEC3AudioInfo) {
"\n"));
}
+class LiveSegmentTimelineTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ FLAGS_segment_template_constant_duration = true;
+ media_info_.set_segment_template_url("$Number$.m4s");
+ }
+
+ void TearDown() override { FLAGS_segment_template_constant_duration = false; }
+
+ MediaInfo media_info_;
+};
+
+TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) {
+ const uint32_t kStartNumber = 1;
+ const uint64_t kStartTime = 0;
+ const uint64_t kDuration = 100;
+ const uint64_t kRepeat = 9;
+
+ std::list segment_infos = {
+ {kStartTime, kDuration, kRepeat},
+ };
+ RepresentationXmlNode representation;
+ ASSERT_TRUE(
+ representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
+
+ EXPECT_THAT(
+ representation.GetRawPtr(),
+ XmlNodeEqual(""
+ " "
+ ""));
+}
+
+TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) {
+ const uint32_t kStartNumber = 1;
+ const uint64_t kNonZeroStartTime = 500;
+ const uint64_t kDuration = 100;
+ const uint64_t kRepeat = 9;
+
+ std::list segment_infos = {
+ {kNonZeroStartTime, kDuration, kRepeat},
+ };
+ RepresentationXmlNode representation;
+ ASSERT_TRUE(
+ representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
+
+ EXPECT_THAT(representation.GetRawPtr(),
+ XmlNodeEqual(
+ ""
+ " "
+ " "
+ " "
+ " "
+ " "
+ ""));
+}
+
+TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) {
+ const uint32_t kStartNumber = 6;
+ const uint64_t kNonZeroStartTime = 500;
+ const uint64_t kDuration = 100;
+ const uint64_t kRepeat = 9;
+
+ std::list segment_infos = {
+ {kNonZeroStartTime, kDuration, kRepeat},
+ };
+ RepresentationXmlNode representation;
+ ASSERT_TRUE(
+ representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
+
+ EXPECT_THAT(
+ representation.GetRawPtr(),
+ XmlNodeEqual(""
+ " "
+ ""));
+}
+
+TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) {
+ const uint32_t kStartNumber = 1;
+
+ const uint64_t kStartTime1 = 0;
+ const uint64_t kDuration1 = 100;
+ const uint64_t kRepeat1 = 9;
+
+ const uint64_t kStartTime2 = kStartTime1 + (kRepeat1 + 1) * kDuration1;
+ const uint64_t kDuration2 = 200;
+ const uint64_t kRepeat2 = 0;
+
+ std::list segment_infos = {
+ {kStartTime1, kDuration1, kRepeat1},
+ {kStartTime2, kDuration2, kRepeat2},
+ };
+ RepresentationXmlNode representation;
+ ASSERT_TRUE(
+ representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
+
+ EXPECT_THAT(
+ representation.GetRawPtr(),
+ XmlNodeEqual(""
+ " "
+ ""));
+}
+
+TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) {
+ const uint32_t kStartNumber = 1;
+
+ const uint64_t kStartTime1 = 0;
+ const uint64_t kDuration1 = 100;
+ const uint64_t kRepeat1 = 9;
+
+ const uint64_t kStartTime2 = kStartTime1 + (kRepeat1 + 1) * kDuration1;
+ const uint64_t kDuration2 = 200;
+ const uint64_t kRepeat2 = 1;
+
+ std::list segment_infos = {
+ {kStartTime1, kDuration1, kRepeat1},
+ {kStartTime2, kDuration2, kRepeat2},
+ };
+ RepresentationXmlNode representation;
+ ASSERT_TRUE(
+ representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
+
+ EXPECT_THAT(representation.GetRawPtr(),
+ XmlNodeEqual(
+ ""
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ ""));
+}
+
+TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) {
+ const uint32_t kStartNumber = 1;
+
+ const uint64_t kStartTime1 = 0;
+ const uint64_t kDuration1 = 100;
+ const uint64_t kRepeat1 = 9;
+
+ const uint64_t kGap = 100;
+ const uint64_t kStartTime2 = kGap + kStartTime1 + (kRepeat1 + 1) * kDuration1;
+ const uint64_t kDuration2 = 200;
+ const uint64_t kRepeat2 = 0;
+
+ std::list segment_infos = {
+ {kStartTime1, kDuration1, kRepeat1},
+ {kStartTime2, kDuration2, kRepeat2},
+ };
+ RepresentationXmlNode representation;
+ ASSERT_TRUE(
+ representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber));
+
+ EXPECT_THAT(representation.GetRawPtr(),
+ XmlNodeEqual(
+ ""
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ ""));
+}
+
} // namespace xml
} // namespace shaka
diff --git a/packager/mpd/mpd.gyp b/packager/mpd/mpd.gyp
index 10156a3b02..a89a195652 100644
--- a/packager/mpd/mpd.gyp
+++ b/packager/mpd/mpd.gyp
@@ -104,6 +104,7 @@
'../media/test/media_test.gyp:run_tests_with_atexit_manager',
'../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
+ '../third_party/gflags/gflags.gyp:gflags',
'mpd_builder',
'mpd_mocks',
'mpd_util',
diff --git a/packager/mpd/public/mpd_params.h b/packager/mpd/public/mpd_params.h
index 20ecd6152c..b92d5d447d 100644
--- a/packager/mpd/public/mpd_params.h
+++ b/packager/mpd/public/mpd_params.h
@@ -59,6 +59,16 @@ struct MpdParams {
bool generate_static_live_mpd = false;
/// Try to generate DASH-IF IOP compliant MPD.
bool generate_dash_if_iop_compliant_mpd = true;
+ /// For live profile only.
+ /// If enabled, segments with close duration (i.e. with difference less than
+ /// one sample) are considered to have the same duration. This enables
+ /// MPD generator to generate less SegmentTimeline entries. If all segments
+ /// are of the same duration except the last one, we will do further
+ /// optimization to use SegmentTemplate@duration instead and omit
+ /// SegmentTimeline completely.
+ /// Ignored if $Time$ is used in segment template, since $Time$ requires
+ /// accurate Segment Timeline.
+ bool allow_approximate_segment_timeline = false;
};
} // namespace shaka
diff --git a/packager/packager.cc b/packager/packager.cc
index 8c7341a6a8..8295a60fe9 100644
--- a/packager/packager.cc
+++ b/packager/packager.cc
@@ -858,8 +858,10 @@ Status Packager::Initialize(
if (!mpd_params.mpd_output.empty()) {
const bool on_demand_dash_profile =
stream_descriptors.begin()->segment_template.empty();
- const MpdOptions mpd_options =
- media::GetMpdOptions(on_demand_dash_profile, mpd_params);
+ const double target_segment_duration =
+ packaging_params.chunking_params.segment_duration_in_seconds;
+ const MpdOptions mpd_options = media::GetMpdOptions(
+ on_demand_dash_profile, mpd_params, target_segment_duration);
internal->mpd_notifier.reset(new SimpleMpdNotifier(mpd_options));
if (!internal->mpd_notifier->Init()) {
LOG(ERROR) << "MpdNotifier failed to initialize.";