diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index 2254dbe013..832b5fd85b 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -269,8 +269,9 @@ xml::scoped_xml_ptr Representation::GetXml() { } if (HasLiveOnlyFields(media_info_) && - !representation.AddLiveOnlyInfo(media_info_, segment_infos_, - start_number_)) { + !representation.AddLiveOnlyInfo( + media_info_, segment_infos_, start_number_, + mpd_options_.mpd_params.target_segment_duration)) { LOG(ERROR) << "Failed to add Live info."; return xml::scoped_xml_ptr(); } @@ -333,7 +334,19 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { const int64_t adjusted_duration = AdjustDuration(duration); if (segment_infos_.empty()) { - start_number_ = start_time / duration; + const int64_t scaled_target_duration = + mpd_options_.mpd_params.target_segment_duration * + media_info_.reference_time_scale(); + + if (mpd_options_.mpd_params.target_segment_duration > 0 && + mpd_options_.mpd_params.allow_approximate_segment_timeline) { + if (adjusted_duration == scaled_target_duration) { + start_number_ = start_time / scaled_target_duration + 1; + } else { + start_number_ = start_time / scaled_target_duration; + } + stream_just_started_ = true; + } } if (!segment_infos_.empty()) { @@ -352,6 +365,19 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { if (ApproximiatelyEqual(segment_end_time_for_same_duration, actual_segment_end_time)) { ++segment_infos_.back().repeat; + if (mpd_options_.mpd_params.allow_approximate_segment_timeline && + mpd_options_.mpd_params.target_segment_duration > 0 && + stream_just_started_ && segment_infos_.size() == 2) { + const SegmentInfo& first_segment = segment_infos_.front(); + const SegmentInfo& last_segment = segment_infos_.back(); + if (first_segment.repeat == 0 && + first_segment.duration < last_segment.duration && + last_segment.repeat > 0) { + segment_infos_.pop_front(); + start_number_ = last_segment.start_time / last_segment.duration + 1; + } + stream_just_started_ = false; + } } else { segment_infos_.push_back( {previous_segment_end_time, diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h index 813d43edc5..91c24c30ae 100644 --- a/packager/mpd/base/representation.h +++ b/packager/mpd/base/representation.h @@ -9,16 +9,16 @@ #ifndef PACKAGER_MPD_BASE_REPRESENTATION_H_ #define PACKAGER_MPD_BASE_REPRESENTATION_H_ -#include "packager/mpd/base/bandwidth_estimator.h" -#include "packager/mpd/base/media_info.pb.h" -#include "packager/mpd/base/segment_info.h" -#include "packager/mpd/base/xml/scoped_xml_ptr.h" - #include #include #include +#include "packager/mpd/base/bandwidth_estimator.h" +#include "packager/mpd/base/media_info.pb.h" +#include "packager/mpd/base/segment_info.h" +#include "packager/mpd/base/xml/scoped_xml_ptr.h" + namespace shaka { struct ContentProtectionElement; @@ -231,6 +231,8 @@ class Representation { // Starts from 1. uint32_t start_number_ = 1; + bool stream_just_started_ = false; + // If this is not null, then Representation is responsible for calling the // right methods at right timings. std::unique_ptr state_change_listener_; diff --git a/packager/mpd/base/representation_unittest.cc b/packager/mpd/base/representation_unittest.cc index c9a9f97684..4f26bc6199 100644 --- a/packager/mpd/base/representation_unittest.cc +++ b/packager/mpd/base/representation_unittest.cc @@ -799,12 +799,16 @@ TEST_P(ApproximateSegmentTimelineTest, if (allow_approximate_segment_timeline_) { expected_s_elements = base::StringPrintf( kSElementTemplateWithoutR, kStartTime, kScaledTargetSegmentDuration); + EXPECT_THAT(representation_->GetXml().get(), + XmlNodeEqual(SegmentTimelineTestBase::ExpectedXml( + expected_s_elements, 1235))); } else { expected_s_elements = base::StringPrintf(kSElementTemplateWithoutR, kStartTime, kDurationSmaller); + EXPECT_THAT(representation_->GetXml().get(), + XmlNodeEqual(SegmentTimelineTestBase::ExpectedXml( + expected_s_elements, 1))); } - EXPECT_THAT(representation_->GetXml().get(), - XmlNodeEqual(ExpectedXml(expected_s_elements))); } TEST_P(ApproximateSegmentTimelineTest, SegmentsWithSimilarDurations) { diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc index 525beb3760..74aa084414 100644 --- a/packager/mpd/base/xml/xml_node.cc +++ b/packager/mpd/base/xml/xml_node.cc @@ -44,18 +44,11 @@ std::string RangeToString(const Range& range) { base::Uint64ToString(range.end()); } -bool ApproximiatelyEqual(int64_t time1, int64_t time2) { - const double kErrorThresholdSeconds = 0.05; - - const uint32_t error_threshold = - static_cast(kErrorThresholdSeconds * time1); - return std::abs(time1 - time2) <= error_threshold; -} - // 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) { + uint32_t start_number, + const double target_duration) { if (!FLAGS_segment_template_constant_duration) return false; @@ -64,15 +57,22 @@ bool IsTimelineConstantDuration(const std::list& segment_infos, return false; const SegmentInfo& first_segment = segment_infos.front(); - if (!ApproximiatelyEqual(first_segment.start_time, - first_segment.duration * start_number)) - return false; + + if (first_segment.duration < target_duration) { + if (static_cast(first_segment.start_time / target_duration) != + start_number) + return false; + } else { + if (static_cast(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 || first_segment.repeat == 0)) + if (last_segment.repeat != 0) return false; const int64_t expected_last_segment_start_time = @@ -414,7 +414,8 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) { bool RepresentationXmlNode::AddLiveOnlyInfo( const MediaInfo& media_info, const std::list& segment_infos, - uint32_t start_number) { + uint32_t start_number, + const double target_duration) { XmlNode segment_template("SegmentTemplate"); if (media_info.has_reference_time_scale()) { segment_template.SetIntegerAttribute("timescale", @@ -440,9 +441,16 @@ bool RepresentationXmlNode::AddLiveOnlyInfo( 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); + if (IsTimelineConstantDuration( + segment_infos, start_number, + target_duration * media_info.reference_time_scale())) { + if (target_duration > 0) { + segment_template.SetIntegerAttribute( + "duration", target_duration * media_info.reference_time_scale()); + } else { + segment_template.SetIntegerAttribute("duration", + segment_infos.front().duration); + } if (FLAGS_dash_add_last_segment_number_when_needed) { uint32_t last_segment_number = start_number - 1; for (const auto& segment_info_element : segment_infos) diff --git a/packager/mpd/base/xml/xml_node.h b/packager/mpd/base/xml/xml_node.h index 976730d4d3..858a56fb2e 100644 --- a/packager/mpd/base/xml/xml_node.h +++ b/packager/mpd/base/xml/xml_node.h @@ -186,7 +186,8 @@ class RepresentationXmlNode : public RepresentationBaseXmlNode { /// SegmentInfos are sorted by its start time. bool AddLiveOnlyInfo(const MediaInfo& media_info, const std::list& segment_infos, - uint32_t start_number); + uint32_t start_number, + const double target_duration); private: // Add AudioChannelConfiguration element. Note that it is a required element diff --git a/packager/mpd/base/xml/xml_node_unittest.cc b/packager/mpd/base/xml/xml_node_unittest.cc index ec2abf83f1..fbf5083165 100644 --- a/packager/mpd/base/xml/xml_node_unittest.cc +++ b/packager/mpd/base/xml/xml_node_unittest.cc @@ -4,6 +4,8 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +#include "packager/mpd/base/xml/xml_node.h" + #include #include #include @@ -14,7 +16,6 @@ #include "packager/base/logging.h" #include "packager/base/strings/string_util.h" #include "packager/mpd/base/segment_info.h" -#include "packager/mpd/base/xml/xml_node.h" #include "packager/mpd/test/xml_compare.h" DECLARE_bool(segment_template_constant_duration); @@ -57,7 +58,6 @@ TEST(XmlNodeTest, MetaTestXmlElementsEqual) { " \n" ""; - // This is same as kXml1 but the attributes are reordered. Note that the // children are not reordered. static const char kXml1AttributeReorder[] = @@ -134,9 +134,8 @@ TEST(XmlNodeTest, MetaTestXmlElementsEqual) { // But if it is run on for the first XML, it will return "content1", but // for second XML will return "c". TEST(XmlNodeTest, MetaTestXmlEqualDifferentContent) { - ASSERT_FALSE(XmlEqual( - "content1content2", - "content1content2")); + ASSERT_FALSE(XmlEqual("content1content2", + "content1content2")); } TEST(XmlNodeTest, ExtractReferencedNamespaces) { @@ -252,8 +251,8 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) { {kStartTime, kDuration, kRepeat}, }; RepresentationXmlNode representation; - ASSERT_TRUE( - representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); + ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, + kStartNumber, 0)); EXPECT_THAT( representation.GetRawPtr(), @@ -273,8 +272,8 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) { {kNonZeroStartTime, kDuration, kRepeat}, }; RepresentationXmlNode representation; - ASSERT_TRUE( - representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); + ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, + kStartNumber, 0)); EXPECT_THAT(representation.GetRawPtr(), XmlNodeEqual( @@ -297,8 +296,8 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) { {kNonZeroStartTime, kDuration, kRepeat}, }; RepresentationXmlNode representation; - ASSERT_TRUE( - representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); + ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, + kStartNumber, 0)); EXPECT_THAT( representation.GetRawPtr(), @@ -324,8 +323,8 @@ TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) { {kStartTime2, kDuration2, kRepeat2}, }; RepresentationXmlNode representation; - ASSERT_TRUE( - representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); + ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, + kStartNumber, 0)); EXPECT_THAT( representation.GetRawPtr(), @@ -351,8 +350,8 @@ TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) { {kStartTime2, kDuration2, kRepeat2}, }; RepresentationXmlNode representation; - ASSERT_TRUE( - representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); + ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, + kStartNumber, 0)); EXPECT_THAT(representation.GetRawPtr(), XmlNodeEqual( @@ -383,8 +382,8 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) { {kStartTime2, kDuration2, kRepeat2}, }; RepresentationXmlNode representation; - ASSERT_TRUE( - representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); + ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, + kStartNumber, 0)); EXPECT_THAT(representation.GetRawPtr(), XmlNodeEqual( @@ -398,31 +397,31 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) { "")); } -TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { - 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; - FLAGS_dash_add_last_segment_number_when_needed = true; - - ASSERT_TRUE( - representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); - - EXPECT_THAT( - representation.GetRawPtr(), - XmlNodeEqual("" - "" - " " - "")); - FLAGS_dash_add_last_segment_number_when_needed = false; -} +TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { + 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; + FLAGS_dash_add_last_segment_number_when_needed = true; + + ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, + kStartNumber, 0)); + + EXPECT_THAT( + representation.GetRawPtr(), + XmlNodeEqual("" + "" + " " + "")); + FLAGS_dash_add_last_segment_number_when_needed = false; +} } // namespace xml } // namespace shaka