diff --git a/docs/source/options/dash_options.rst b/docs/source/options/dash_options.rst index 0428e993d1..a36787aefd 100644 --- a/docs/source/options/dash_options.rst +++ b/docs/source/options/dash_options.rst @@ -9,36 +9,41 @@ DASH options that if segment_template is not specified, shaka-packager always generates static mpd regardless of the value of this flag. ---mpd_output +--mpd_output {file_path} MPD output file name. ---base_urls +--base_urls {url}[,{url}]... Comma separated BaseURLs for the MPD. The values will be added as element(s) immediately under the element. ---min_buffer_time +--min_buffer_time {seconds} Specifies, in seconds, a common duration used in the definition of the MPD Representation data rate. ---minimum_update_period +--minimum_update_period {seconds} Indicates to the player how often to refresh the media presentation description in seconds. This value is used for dynamic MPD only. ---suggested_presentation_delay +--suggested_presentation_delay {seconds} Specifies a delay, in seconds, to be added to the media presentation time. This value is used for dynamic MPD only. ---time_shift_buffer_depth +--time_shift_buffer_depth {seconds} Guaranteed duration of the time shifting buffer for dynamic media presentations, in seconds. ---default_language +--utc_timing {scheme_id_uri}={value}[,{scheme_id_uri}={value}]... + + Comma separated UTCTiming schemeIdUri and value pairs for the MPD. + This value is used for dynamic MPD only. + +--default_language {language} Any audio/text tracks tagged with this language will have in the manifest. This allows the player to diff --git a/packager/app/mpd_flags.cc b/packager/app/mpd_flags.cc index a2674dc071..01a260d217 100644 --- a/packager/app/mpd_flags.cc +++ b/packager/app/mpd_flags.cc @@ -43,6 +43,10 @@ DEFINE_double(suggested_presentation_delay, 0.0, "Specifies a delay, in seconds, to be added to the media " "presentation time. This value is used for dynamic MPD only."); +DEFINE_string(utc_timings, + "", + "Comma separated UTCTiming schemeIdUri and value pairs for the " + "MPD. This value is used for dynamic MPD only."); DEFINE_bool(generate_dash_if_iop_compliant_mpd, true, "Try to generate DASH-IF IOP compliant MPD. This is best effort " diff --git a/packager/app/mpd_flags.h b/packager/app/mpd_flags.h index 21e25cc055..8377df47ab 100644 --- a/packager/app/mpd_flags.h +++ b/packager/app/mpd_flags.h @@ -18,6 +18,7 @@ DECLARE_string(base_urls); DECLARE_double(minimum_update_period); DECLARE_double(min_buffer_time); DECLARE_double(suggested_presentation_delay); +DECLARE_string(utc_timings); DECLARE_bool(generate_dash_if_iop_compliant_mpd); #endif // APP_MPD_FLAGS_H_ diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index b65b9e02fe..e3a67227d2 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -396,17 +396,30 @@ base::Optional GetPackagingParams() { packaging_params.output_media_info = FLAGS_output_media_info; MpdParams& mpd_params = packaging_params.mpd_params; - mpd_params.generate_static_live_mpd = FLAGS_generate_static_mpd; mpd_params.mpd_output = FLAGS_mpd_output; mpd_params.base_urls = base::SplitString( - FLAGS_base_urls, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + FLAGS_base_urls, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + mpd_params.min_buffer_time = FLAGS_min_buffer_time; + mpd_params.minimum_update_period = FLAGS_minimum_update_period; + mpd_params.suggested_presentation_delay = FLAGS_suggested_presentation_delay; + mpd_params.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth; + + if (!FLAGS_utc_timings.empty()) { + base::StringPairs pairs; + if (!base::SplitStringIntoKeyValuePairs(FLAGS_utc_timings, '=', ',', + &pairs)) { + LOG(ERROR) << "Invalid --utc_timings scheme_id_uri/value pairs."; + return base::nullopt; + } + for (const auto& string_pair : pairs) { + mpd_params.utc_timings.push_back({string_pair.first, string_pair.second}); + } + } + + mpd_params.default_language = FLAGS_default_language; + 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.minimum_update_period = FLAGS_minimum_update_period; - mpd_params.min_buffer_time = FLAGS_min_buffer_time; - mpd_params.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth; - mpd_params.suggested_presentation_delay = FLAGS_suggested_presentation_delay; - mpd_params.default_language = FLAGS_default_language; HlsParams& hls_params = packaging_params.hls_params; if (!GetHlsPlaylistType(FLAGS_hls_playlist_type, &hls_params.playlist_type)) { diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 47429d2db2..93be10d666 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -273,6 +273,7 @@ class PackagerAppTest(unittest.TestCase): output_hls=False, hls_playlist_type=None, time_shift_buffer_depth=0.0, + utc_timings=None, generate_static_mpd=False, ad_cues=None, use_fake_clock=True): @@ -341,6 +342,9 @@ class PackagerAppTest(unittest.TestCase): else: flags += ['--mpd_output', self.mpd_output] + if utc_timings: + flags += ['--utc_timings', utc_timings] + if generate_static_mpd: flags += ['--generate_static_mpd'] @@ -384,7 +388,7 @@ class PackagerAppTest(unittest.TestCase): else: match = filecmp.cmp(test_output, golden_file) if not match: - output, error = self._GitDiff(test_output, golden_file) + output, error = self._GitDiff(golden_file, test_output) command_line = self.packager.GetCommandLine() failure_message = '\n'.join([ output, @@ -471,7 +475,7 @@ class PackagerAppTest(unittest.TestCase): actual_file = os.path.join(out_dir, diff_file) expected_file = os.path.join(gold_dir, diff_file) - output, error = self._GitDiff(actual_file, expected_file) + output, error = self._GitDiff(expected_file, actual_file) if output: failure_messages += [output] @@ -1126,7 +1130,13 @@ class PackagerFunctionalTest(PackagerAppTest): def testPackageLiveProfile(self): self.assertPackageSuccess( - self._GetStreams(['audio', 'video'], segmented=True), self._GetFlags()) + self._GetStreams(['audio', 'video'], segmented=True), + self._GetFlags( + utc_timings= + 'urn:mpeg:dash:utc:http-xsdate:2014=' + 'http://foo.bar/my_body_is_the_current_date_and_time,' + 'urn:mpeg:dash:utc:http-head:2014=' + 'http://foo.bar/check_me_for_the_date_header')) self._CheckTestResults('live-profile') def testPackageLiveStaticProfile(self): diff --git a/packager/app/test/testdata/live-profile/output.mpd b/packager/app/test/testdata/live-profile/output.mpd index fb0024dde8..8482505bfb 100644 --- a/packager/app/test/testdata/live-profile/output.mpd +++ b/packager/app/test/testdata/live-profile/output.mpd @@ -25,4 +25,6 @@ + + diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index aceba15600..2845ed2517 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -215,6 +215,8 @@ xmlDocPtr MpdBuilder::GenerateMpd() { break; case MpdType::kDynamic: AddDynamicMpdInfo(&mpd); + // Must be after Period element. + AddUtcTiming(&mpd); break; default: NOTREACHED() << "Unknown MPD type: " @@ -302,6 +304,19 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) { mpd_options_.mpd_params.suggested_presentation_delay, mpd_node); } +void MpdBuilder::AddUtcTiming(XmlNode* mpd_node) { + DCHECK(mpd_node); + DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type); + + for (const MpdParams::UtcTiming& utc_timing : + mpd_options_.mpd_params.utc_timings) { + XmlNode utc_timing_node("UTCTiming"); + utc_timing_node.SetStringAttribute("schemeIdUri", utc_timing.scheme_id_uri); + utc_timing_node.SetStringAttribute("value", utc_timing.value); + mpd_node->AddChild(utc_timing_node.PassScopedPtr()); + } +} + float MpdBuilder::GetStaticMpdDuration() { DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type); diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index 6d3993bc79..9a81df8cb9 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -100,6 +100,9 @@ class MpdBuilder { // Same as AddStaticMpdInfo() but for 'dynamic' MPDs. void AddDynamicMpdInfo(xml::XmlNode* mpd_node); + // Add UTCTiming element if utc timing is provided. + void AddUtcTiming(xml::XmlNode* mpd_node); + float GetStaticMpdDuration(); // Set MPD attributes for dynamic profile MPD. Uses non-zero |mpd_options_| as diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 288354025c..0d26da23da 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -272,11 +272,21 @@ TEST_F(LiveMpdBuilderTest, DynamicCheckMpdAttributes) { " type=\"dynamic\"" " publishTime=\"2016-01-11T15:10:24Z\"" " availabilityStartTime=\"2011-12-25T12:30:00\"" - " minimumUpdatePeriod=\"PT2S\"/>\n"; + " minimumUpdatePeriod=\"PT2S\">\n" + " \n" + " \n" + "\n"; std::string mpd_doc; mutable_mpd_options()->mpd_type = MpdType::kDynamic; mutable_mpd_options()->mpd_params.minimum_update_period = 2; + mutable_mpd_options()->mpd_params.utc_timings = { + {"urn:mpeg:dash:utc:http-xsdate:2014", + "http://foo.bar/my_body_is_the_current_date_and_time"}, + {"urn:mpeg:dash:utc:http-head:2014", + "http://foo.bar/check_me_for_the_date_header"}}; ASSERT_TRUE(mpd_.ToString(&mpd_doc)); ASSERT_EQ(kExpectedOutput, mpd_doc); } @@ -298,6 +308,15 @@ TEST_F(LiveMpdBuilderTest, StaticCheckMpdAttributes) { std::string mpd_doc; mutable_mpd_options()->mpd_type = MpdType::kStatic; + + // Ignored in static MPD. + mutable_mpd_options()->mpd_params.minimum_update_period = 2; + mutable_mpd_options()->mpd_params.utc_timings = { + {"urn:mpeg:dash:utc:http-xsdate:2014", + "http://foo.bar/my_body_is_the_current_date_and_time"}, + {"urn:mpeg:dash:utc:http-head:2014", + "http://foo.bar/check_me_for_the_date_header"}}; + ASSERT_TRUE(mpd_.ToString(&mpd_doc)); ASSERT_EQ(kExpectedOutput, mpd_doc); } diff --git a/packager/mpd/public/mpd_params.h b/packager/mpd/public/mpd_params.h index 459e632305..30aff9ea47 100644 --- a/packager/mpd/public/mpd_params.h +++ b/packager/mpd/public/mpd_params.h @@ -25,25 +25,31 @@ struct MpdParams { /// providing playout begins at min_buffer_time after the first bit is /// received. double min_buffer_time = 2.0; - /// Generate static MPD for live profile. Note that this flag has no effect - /// for on-demand profile, in which case static MPD is always used. - bool generate_static_live_mpd = false; - /// Set MPD@timeShiftBufferDepth attribute, which is the guaranteed duration - /// of the time shifting buffer for 'dynamic' media presentations, in seconds. - double time_shift_buffer_depth = 0; + /// Set MPD@minimumUpdatePeriod attribute, which indicates to the player how + /// often to refresh the MPD in seconds. For dynamic MPD only. + double minimum_update_period = 0; /// Set MPD@suggestedPresentationDelay attribute. For 'dynamic' media /// presentations, it specifies a delay, in seconds, to be added to the media /// presentation time. The attribute is not set if the value is 0; the client /// is expected to choose a suitable value in this case. static constexpr double kSuggestedPresentationDelayNotSet = 0; double suggested_presentation_delay = kSuggestedPresentationDelayNotSet; - /// Set MPD@minimumUpdatePeriod attribute, which indicates to the player how - /// often to refresh the MPD in seconds. For dynamic MPD only. - double minimum_update_period = 0; + /// Set MPD@timeShiftBufferDepth attribute, which is the guaranteed duration + /// of the time shifting buffer for 'dynamic' media presentations, in seconds. + double time_shift_buffer_depth = 0; + /// UTCTimings. For dynamic MPD only. + struct UtcTiming { + std::string scheme_id_uri; + std::string value; + }; + std::vector utc_timings; /// The tracks tagged with this language will have /// in the manifest. This allows the player to choose the correct default /// language for the content. std::string default_language; + /// Generate static MPD for live profile. Note that this flag has no effect + /// for on-demand profile, in which case static MPD is always used. + bool generate_static_live_mpd = false; /// Try to generate DASH-IF IOP compliant MPD. bool generate_dash_if_iop_compliant_mpd = true; };