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.";