Support UTCTiming

UTCTiming schemeIdUri and value pairs can be provided to packager using
--utc_timings flag. It should be comma separated list of
schemeIdUri=value pairs.

Note that urn:mpeg:dash:utc:direct:2014 scheme is not supported as it
requires the MPD to be dynamically generated on the fly when MPD is
served to client.

Fixes #311.

Change-Id: Ibc07af8a6d8b2b6261ba3ecd2c02f23809f96614
This commit is contained in:
KongQun Yang 2018-03-16 18:37:53 -07:00
parent 634ee65663
commit 222737d5b5
10 changed files with 105 additions and 27 deletions

View File

@ -9,36 +9,41 @@ DASH options
that if segment_template is not specified, shaka-packager always generates that if segment_template is not specified, shaka-packager always generates
static mpd regardless of the value of this flag. static mpd regardless of the value of this flag.
--mpd_output <file_path> --mpd_output {file_path}
MPD output file name. MPD output file name.
--base_urls <comma separated url> --base_urls {url}[,{url}]...
Comma separated BaseURLs for the MPD. The values will be added as <BaseURL> Comma separated BaseURLs for the MPD. The values will be added as <BaseURL>
element(s) immediately under the <MPD> element. element(s) immediately under the <MPD> element.
--min_buffer_time <seconds> --min_buffer_time {seconds}
Specifies, in seconds, a common duration used in the definition of the MPD Specifies, in seconds, a common duration used in the definition of the MPD
Representation data rate. Representation data rate.
--minimum_update_period <seconds> --minimum_update_period {seconds}
Indicates to the player how often to refresh the media presentation Indicates to the player how often to refresh the media presentation
description in seconds. This value is used for dynamic MPD only. description in seconds. This value is used for dynamic MPD only.
--suggested_presentation_delay <seconds> --suggested_presentation_delay {seconds}
Specifies a delay, in seconds, to be added to the media presentation time. Specifies a delay, in seconds, to be added to the media presentation time.
This value is used for dynamic MPD only. This value is used for dynamic MPD only.
--time_shift_buffer_depth <seconds> --time_shift_buffer_depth {seconds}
Guaranteed duration of the time shifting buffer for dynamic media Guaranteed duration of the time shifting buffer for dynamic media
presentations, in seconds. presentations, in seconds.
--default_language <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 Any audio/text tracks tagged with this language will have
<Role ... value=\"main\" /> in the manifest. This allows the player to <Role ... value=\"main\" /> in the manifest. This allows the player to

View File

@ -43,6 +43,10 @@ DEFINE_double(suggested_presentation_delay,
0.0, 0.0,
"Specifies a delay, in seconds, to be added to the media " "Specifies a delay, in seconds, to be added to the media "
"presentation time. This value is used for dynamic MPD only."); "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, DEFINE_bool(generate_dash_if_iop_compliant_mpd,
true, true,
"Try to generate DASH-IF IOP compliant MPD. This is best effort " "Try to generate DASH-IF IOP compliant MPD. This is best effort "

View File

@ -18,6 +18,7 @@ DECLARE_string(base_urls);
DECLARE_double(minimum_update_period); DECLARE_double(minimum_update_period);
DECLARE_double(min_buffer_time); DECLARE_double(min_buffer_time);
DECLARE_double(suggested_presentation_delay); DECLARE_double(suggested_presentation_delay);
DECLARE_string(utc_timings);
DECLARE_bool(generate_dash_if_iop_compliant_mpd); DECLARE_bool(generate_dash_if_iop_compliant_mpd);
#endif // APP_MPD_FLAGS_H_ #endif // APP_MPD_FLAGS_H_

View File

@ -396,17 +396,30 @@ base::Optional<PackagingParams> GetPackagingParams() {
packaging_params.output_media_info = FLAGS_output_media_info; packaging_params.output_media_info = FLAGS_output_media_info;
MpdParams& mpd_params = packaging_params.mpd_params; 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.mpd_output = FLAGS_mpd_output;
mpd_params.base_urls = base::SplitString( 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 = mpd_params.generate_dash_if_iop_compliant_mpd =
FLAGS_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; HlsParams& hls_params = packaging_params.hls_params;
if (!GetHlsPlaylistType(FLAGS_hls_playlist_type, &hls_params.playlist_type)) { if (!GetHlsPlaylistType(FLAGS_hls_playlist_type, &hls_params.playlist_type)) {

View File

@ -273,6 +273,7 @@ class PackagerAppTest(unittest.TestCase):
output_hls=False, output_hls=False,
hls_playlist_type=None, hls_playlist_type=None,
time_shift_buffer_depth=0.0, time_shift_buffer_depth=0.0,
utc_timings=None,
generate_static_mpd=False, generate_static_mpd=False,
ad_cues=None, ad_cues=None,
use_fake_clock=True): use_fake_clock=True):
@ -341,6 +342,9 @@ class PackagerAppTest(unittest.TestCase):
else: else:
flags += ['--mpd_output', self.mpd_output] flags += ['--mpd_output', self.mpd_output]
if utc_timings:
flags += ['--utc_timings', utc_timings]
if generate_static_mpd: if generate_static_mpd:
flags += ['--generate_static_mpd'] flags += ['--generate_static_mpd']
@ -384,7 +388,7 @@ class PackagerAppTest(unittest.TestCase):
else: else:
match = filecmp.cmp(test_output, golden_file) match = filecmp.cmp(test_output, golden_file)
if not match: if not match:
output, error = self._GitDiff(test_output, golden_file) output, error = self._GitDiff(golden_file, test_output)
command_line = self.packager.GetCommandLine() command_line = self.packager.GetCommandLine()
failure_message = '\n'.join([ failure_message = '\n'.join([
output, output,
@ -471,7 +475,7 @@ class PackagerAppTest(unittest.TestCase):
actual_file = os.path.join(out_dir, diff_file) actual_file = os.path.join(out_dir, diff_file)
expected_file = os.path.join(gold_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: if output:
failure_messages += [output] failure_messages += [output]
@ -1126,7 +1130,13 @@ class PackagerFunctionalTest(PackagerAppTest):
def testPackageLiveProfile(self): def testPackageLiveProfile(self):
self.assertPackageSuccess( 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') self._CheckTestResults('live-profile')
def testPackageLiveStaticProfile(self): def testPackageLiveStaticProfile(self):

View File

@ -25,4 +25,6 @@
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>
</Period> </Period>
<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-xsdate:2014" value="http://foo.bar/my_body_is_the_current_date_and_time"/>
<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-head:2014" value="http://foo.bar/check_me_for_the_date_header"/>
</MPD> </MPD>

View File

@ -215,6 +215,8 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
break; break;
case MpdType::kDynamic: case MpdType::kDynamic:
AddDynamicMpdInfo(&mpd); AddDynamicMpdInfo(&mpd);
// Must be after Period element.
AddUtcTiming(&mpd);
break; break;
default: default:
NOTREACHED() << "Unknown MPD type: " NOTREACHED() << "Unknown MPD type: "
@ -302,6 +304,19 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
mpd_options_.mpd_params.suggested_presentation_delay, 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() { float MpdBuilder::GetStaticMpdDuration() {
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type); DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);

View File

@ -100,6 +100,9 @@ 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);
// Add UTCTiming element if utc timing is provided.
void AddUtcTiming(xml::XmlNode* mpd_node);
float GetStaticMpdDuration(); 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

View File

@ -272,11 +272,21 @@ TEST_F(LiveMpdBuilderTest, DynamicCheckMpdAttributes) {
" type=\"dynamic\"" " type=\"dynamic\""
" publishTime=\"2016-01-11T15:10:24Z\"" " publishTime=\"2016-01-11T15:10:24Z\""
" availabilityStartTime=\"2011-12-25T12:30:00\"" " availabilityStartTime=\"2011-12-25T12:30:00\""
" minimumUpdatePeriod=\"PT2S\"/>\n"; " minimumUpdatePeriod=\"PT2S\">\n"
" <UTCTiming schemeIdUri=\"urn:mpeg:dash:utc:http-xsdate:2014\" "
"value=\"http://foo.bar/my_body_is_the_current_date_and_time\"/>\n"
" <UTCTiming schemeIdUri=\"urn:mpeg:dash:utc:http-head:2014\" "
"value=\"http://foo.bar/check_me_for_the_date_header\"/>\n"
"</MPD>\n";
std::string mpd_doc; std::string mpd_doc;
mutable_mpd_options()->mpd_type = MpdType::kDynamic; mutable_mpd_options()->mpd_type = MpdType::kDynamic;
mutable_mpd_options()->mpd_params.minimum_update_period = 2; 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_TRUE(mpd_.ToString(&mpd_doc));
ASSERT_EQ(kExpectedOutput, mpd_doc); ASSERT_EQ(kExpectedOutput, mpd_doc);
} }
@ -298,6 +308,15 @@ TEST_F(LiveMpdBuilderTest, StaticCheckMpdAttributes) {
std::string mpd_doc; std::string mpd_doc;
mutable_mpd_options()->mpd_type = MpdType::kStatic; 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_TRUE(mpd_.ToString(&mpd_doc));
ASSERT_EQ(kExpectedOutput, mpd_doc); ASSERT_EQ(kExpectedOutput, mpd_doc);
} }

View File

@ -25,25 +25,31 @@ struct MpdParams {
/// providing playout begins at min_buffer_time after the first bit is /// providing playout begins at min_buffer_time after the first bit is
/// received. /// received.
double min_buffer_time = 2.0; double min_buffer_time = 2.0;
/// Generate static MPD for live profile. Note that this flag has no effect /// Set MPD@minimumUpdatePeriod attribute, which indicates to the player how
/// for on-demand profile, in which case static MPD is always used. /// often to refresh the MPD in seconds. For dynamic MPD only.
bool generate_static_live_mpd = false; 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;
/// Set MPD@suggestedPresentationDelay attribute. For 'dynamic' media /// Set MPD@suggestedPresentationDelay attribute. For 'dynamic' media
/// presentations, it specifies a delay, in seconds, to be added to the 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 /// presentation time. The attribute is not set if the value is 0; the client
/// is expected to choose a suitable value in this case. /// is expected to choose a suitable value in this case.
static constexpr double kSuggestedPresentationDelayNotSet = 0; static constexpr double kSuggestedPresentationDelayNotSet = 0;
double suggested_presentation_delay = kSuggestedPresentationDelayNotSet; double suggested_presentation_delay = kSuggestedPresentationDelayNotSet;
/// Set MPD@minimumUpdatePeriod attribute, which indicates to the player how /// Set MPD@timeShiftBufferDepth attribute, which is the guaranteed duration
/// often to refresh the MPD in seconds. For dynamic MPD only. /// of the time shifting buffer for 'dynamic' media presentations, in seconds.
double minimum_update_period = 0; double time_shift_buffer_depth = 0;
/// UTCTimings. For dynamic MPD only.
struct UtcTiming {
std::string scheme_id_uri;
std::string value;
};
std::vector<UtcTiming> utc_timings;
/// The tracks tagged with this language will have <Role ... value=\"main\" /> /// The tracks tagged with this language will have <Role ... value=\"main\" />
/// in the manifest. This allows the player to choose the correct default /// in the manifest. This allows the player to choose the correct default
/// language for the content. /// language for the content.
std::string default_language; 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. /// Try to generate DASH-IF IOP compliant MPD.
bool generate_dash_if_iop_compliant_mpd = true; bool generate_dash_if_iop_compliant_mpd = true;
}; };