Prefer Period@duration for static MPD with >1 periods

It is easier to insert Ad Periods with Period@duration compared to
Period@start.

Change-Id: Ib52e81612562bf60b0e0513a09d9a31c42b09604
This commit is contained in:
KongQun Yang 2018-01-24 15:26:37 -08:00
parent 48cb55c8d4
commit 221ac81772
10 changed files with 110 additions and 17 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>--> <!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.76317S"> <MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.76317S">
<Period id="0"> <Period id="0" duration="PT2.06873S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9"> <AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"> <ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
@ -28,7 +28,7 @@
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>
</Period> </Period>
<Period id="1" start="PT2.06873S"> <Period id="1" duration="PT0.694441S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9"> <AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"> <ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>--> <!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-live:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.76317S"> <MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-live:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.76317S">
<Period id="0"> <Period id="0" duration="PT2.06873S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9"> <AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="875099" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1"> <Representation id="0" bandwidth="875099" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
<SegmentTemplate timescale="30000" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="1"> <SegmentTemplate timescale="30000" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="1">
@ -22,7 +22,7 @@
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>
</Period> </Period>
<Period id="1" start="PT2.06873S"> <Period id="1" duration="PT0.694444S">
<AdaptationSet id="1" contentType="audio" segmentAlignment="true"> <AdaptationSet id="1" contentType="audio" segmentAlignment="true">
<Representation id="1" bandwidth="108486" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100"> <Representation id="1" bandwidth="108486" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>

View File

@ -6,6 +6,8 @@
#include "packager/mpd/base/mpd_builder.h" #include "packager/mpd/base/mpd_builder.h"
#include <algorithm>
#include "packager/base/files/file_path.h" #include "packager/base/files/file_path.h"
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
@ -170,6 +172,21 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
return nullptr; return nullptr;
} }
// Prefer Period@duration to Period@start for static MPD with more than one
// periods.
if (mpd_options_.mpd_type == MpdType::kStatic && periods_.size() > 1) {
// The duration of every period is determined by its start_time and next
// period start_time. The code below traverses |periods_| backwards.
double next_period_start_time = GetStaticMpdDuration();
std::for_each(
periods_.rbegin(), periods_.rend(),
[&next_period_start_time](const std::unique_ptr<Period>& period) {
period->set_duration_seconds(next_period_start_time -
period->start_time_in_seconds());
next_period_start_time = period->start_time_in_seconds();
});
}
for (const auto& period : periods_) { for (const auto& period : periods_) {
xml::scoped_xml_ptr<xmlNode> period_node(period->GetXml()); xml::scoped_xml_ptr<xmlNode> period_node(period->GetXml());
if (!period_node || !mpd.AddChild(std::move(period_node))) if (!period_node || !mpd.AddChild(std::move(period_node)))
@ -242,9 +259,8 @@ void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
static const char kStaticMpdType[] = "static"; static const char kStaticMpdType[] = "static";
mpd_node->SetStringAttribute("type", kStaticMpdType); mpd_node->SetStringAttribute("type", kStaticMpdType);
mpd_node->SetStringAttribute( mpd_node->SetStringAttribute("mediaPresentationDuration",
"mediaPresentationDuration", SecondsToXmlDuration(GetStaticMpdDuration()));
SecondsToXmlDuration(GetStaticMpdDuration(mpd_node)));
} }
void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) { void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
@ -290,8 +306,7 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
mpd_options_.mpd_params.suggested_presentation_delay, mpd_node); mpd_options_.mpd_params.suggested_presentation_delay, mpd_node);
} }
float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) { float MpdBuilder::GetStaticMpdDuration() {
DCHECK(mpd_node);
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type); DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
if (periods_.empty()) { if (periods_.empty()) {

View File

@ -100,7 +100,7 @@ class MpdBuilder {
// Same as AddStaticMpdInfo() but for 'dynamic' MPDs. // Same as AddStaticMpdInfo() but for 'dynamic' MPDs.
void AddDynamicMpdInfo(xml::XmlNode* mpd_node); void AddDynamicMpdInfo(xml::XmlNode* mpd_node);
float GetStaticMpdDuration(xml::XmlNode* mpd_node); float GetStaticMpdDuration();
// Set MPD attributes for dynamic profile MPD. Uses non-zero |mpd_options_| as // Set MPD attributes for dynamic profile MPD. Uses non-zero |mpd_options_| as
// well as various calculations to set attributes for the MPD. // well as various calculations to set attributes for the MPD.

View File

@ -4,6 +4,7 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd // https://developers.google.com/open-source/licenses/bsd
#include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "packager/mpd/base/adaptation_set.h" #include "packager/mpd/base/adaptation_set.h"
@ -12,6 +13,8 @@
#include "packager/mpd/test/mpd_builder_test_helper.h" #include "packager/mpd/test/mpd_builder_test_helper.h"
#include "packager/version/version.h" #include "packager/version/version.h"
using ::testing::HasSubstr;
namespace shaka { namespace shaka {
namespace { namespace {
@ -150,7 +153,7 @@ TEST_F(OnDemandMpdBuilderTest, MediaInfoMissingBandwidth) {
ASSERT_FALSE(mpd_.ToString(&mpd_doc)); ASSERT_FALSE(mpd_.ToString(&mpd_doc));
} }
TEST_F(LiveMpdBuilderTest, MultiplePeriodTest) { TEST_F(OnDemandMpdBuilderTest, MultiplePeriodTest) {
const double kPeriodStartTimeSeconds = 1.0; const double kPeriodStartTimeSeconds = 1.0;
Period* period = mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds); Period* period = mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds);
ASSERT_TRUE(period); ASSERT_TRUE(period);
@ -170,6 +173,40 @@ TEST_F(LiveMpdBuilderTest, MultiplePeriodTest) {
ASSERT_EQ(kPeriodStartTimeSeconds3, period3->start_time_in_seconds()); ASSERT_EQ(kPeriodStartTimeSeconds3, period3->start_time_in_seconds());
} }
TEST_F(OnDemandMpdBuilderTest, MultiplePeriodCheckXmlTest) {
const double kPeriodStartTimeSeconds = 0.0;
const double kPeriodStartTimeSeconds2 = 3.1;
const double kPeriodStartTimeSeconds3 = 8.0;
mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds);
mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds2);
mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds3);
std::string mpd_doc;
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
EXPECT_THAT(mpd_doc,
HasSubstr(" <Period id=\"0\" duration=\"PT3.1S\"/>\n"
" <Period id=\"1\" duration=\"PT4.9S\"/>\n"
// There are no Representations so MPD duration is 0,
// which results in a negative duration for the last
// period. This would not happen in practice.
" <Period id=\"2\" duration=\"PT-8S\"/>\n"));
}
TEST_F(LiveMpdBuilderTest, MultiplePeriodCheckXmlTest) {
const double kPeriodStartTimeSeconds = 0.0;
const double kPeriodStartTimeSeconds2 = 3.1;
const double kPeriodStartTimeSeconds3 = 8.0;
mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds);
mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds2);
mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds3);
std::string mpd_doc;
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
EXPECT_THAT(mpd_doc, HasSubstr(" <Period id=\"0\" start=\"PT0S\"/>\n"
" <Period id=\"1\" start=\"PT3.1S\"/>\n"
" <Period id=\"2\" start=\"PT8S\"/>\n"));
}
// Check whether the attributes are set correctly for dynamic <MPD> element. // Check whether the attributes are set correctly for dynamic <MPD> element.
// This test must use ASSERT_EQ for comparison because XmlEqual() cannot // This test must use ASSERT_EQ for comparison because XmlEqual() cannot
// handle namespaces correctly yet. // handle namespaces correctly yet.

View File

@ -94,7 +94,7 @@ AdaptationSet* Period::GetOrCreateAdaptationSet(
return adaptation_set_ptr; return adaptation_set_ptr;
} }
xml::scoped_xml_ptr<xmlNode> Period::GetXml() { xml::scoped_xml_ptr<xmlNode> Period::GetXml() const {
xml::XmlNode period("Period"); xml::XmlNode period("Period");
// Required for 'dynamic' MPDs. // Required for 'dynamic' MPDs.
@ -106,7 +106,10 @@ xml::scoped_xml_ptr<xmlNode> Period::GetXml() {
return nullptr; return nullptr;
} }
if (mpd_options_.mpd_type == MpdType::kDynamic || if (duration_seconds_ != 0) {
period.SetStringAttribute("duration",
SecondsToXmlDuration(duration_seconds_));
} else if (mpd_options_.mpd_type == MpdType::kDynamic ||
start_time_in_seconds_ != 0) { start_time_in_seconds_ != 0) {
period.SetStringAttribute("start", period.SetStringAttribute("start",
SecondsToXmlDuration(start_time_in_seconds_)); SecondsToXmlDuration(start_time_in_seconds_));

View File

@ -48,7 +48,7 @@ class Period {
/// Generates <Period> xml element with its child AdaptationSet elements. /// Generates <Period> xml element with its child AdaptationSet elements.
/// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a /// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a
/// NULL scoped_xml_ptr. /// NULL scoped_xml_ptr.
xml::scoped_xml_ptr<xmlNode> GetXml(); xml::scoped_xml_ptr<xmlNode> GetXml() const;
/// @return The list of AdaptationSets in this Period. /// @return The list of AdaptationSets in this Period.
const std::list<AdaptationSet*> GetAdaptationSets() const; const std::list<AdaptationSet*> GetAdaptationSets() const;
@ -56,6 +56,11 @@ class Period {
/// @return The start time of this Period. /// @return The start time of this Period.
double start_time_in_seconds() const { return start_time_in_seconds_; } double start_time_in_seconds() const { return start_time_in_seconds_; }
/// Set period duration.
void set_duration_seconds(double duration_seconds) {
duration_seconds_ = duration_seconds;
}
protected: protected:
/// @param period_id is an ID number for this Period. /// @param period_id is an ID number for this Period.
/// @param start_time_in_seconds is the start time for this Period. /// @param start_time_in_seconds is the start time for this Period.
@ -102,6 +107,7 @@ class Period {
const uint32_t id_; const uint32_t id_;
const double start_time_in_seconds_; const double start_time_in_seconds_;
double duration_seconds_ = 0;
const MpdOptions& mpd_options_; const MpdOptions& mpd_options_;
base::AtomicSequenceNumber* const adaptation_set_counter_; base::AtomicSequenceNumber* const adaptation_set_counter_;
base::AtomicSequenceNumber* const representation_counter_; base::AtomicSequenceNumber* const representation_counter_;

View File

@ -177,6 +177,38 @@ TEST_P(PeriodTest, DynamicMpdGetXml) {
EXPECT_THAT(testable_period_.GetXml().get(), XmlNodeEqual(kExpectedXml)); EXPECT_THAT(testable_period_.GetXml().get(), XmlNodeEqual(kExpectedXml));
} }
TEST_P(PeriodTest, SetDurationAndGetXml) {
const char kVideoMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(default_adaptation_set_))));
ASSERT_EQ(default_adaptation_set_ptr_,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVideoMediaInfo),
content_protection_in_adaptation_set_));
testable_period_.set_duration_seconds(100.234);
const char kExpectedXml[] =
"<Period id=\"9\" duration=\"PT100.234S\">"
// ContentType and Representation elements are populated after
// Representation::Init() is called.
" <AdaptationSet id=\"0\" contentType=\"\"/>"
"</Period>";
EXPECT_THAT(testable_period_.GetXml().get(), XmlNodeEqual(kExpectedXml));
}
// Verify ForceSetSegmentAlignment is called. // Verify ForceSetSegmentAlignment is called.
TEST_P(PeriodTest, Text) { TEST_P(PeriodTest, Text) {
const char kTextMediaInfo[] = const char kTextMediaInfo[] =

View File

@ -315,7 +315,7 @@ float Representation::GetDurationSeconds() const {
return media_info_.media_duration_seconds(); return media_info_.media_duration_seconds();
} }
bool Representation::HasRequiredMediaInfoFields() { bool Representation::HasRequiredMediaInfoFields() const {
if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) { if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields."; LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields.";
return false; return false;

View File

@ -172,7 +172,7 @@ class Representation {
// Returns true if |media_info_| has required fields to generate a valid // Returns true if |media_info_| has required fields to generate a valid
// Representation. Otherwise returns false. // Representation. Otherwise returns false.
bool HasRequiredMediaInfoFields(); bool HasRequiredMediaInfoFields() const;
// Return false if the segment should be considered a new segment. True if the // Return false if the segment should be considered a new segment. True if the
// segment is contiguous. // segment is contiguous.