diff --git a/app/mpd_flags.cc b/app/mpd_flags.cc index f2693c0234..fb6be2693b 100644 --- a/app/mpd_flags.cc +++ b/app/mpd_flags.cc @@ -29,3 +29,25 @@ DEFINE_string(base_urls, "", "Comma separated BaseURLs for the MPD. The values will be added " "as element(s) immediately under the element."); +DEFINE_double(min_buffer_time, + 2.0, + "Specifies, in seconds, a common duration used in the definition " + "of the MPD Representation data rate."); +DEFINE_double(availability_time_offset, + 10.0, + "Offset with respect to the wall clock time for MPD " + "availabilityStartTime and availabilityEndTime values, in " + " seconds. This value is used for live profile only."); +DEFINE_double(minimum_update_period, + 5.0, + "Indicates to the player how often to refresh the media " + "presentation description in seconds. This value is used for " + "live profile only."); +DEFINE_double(time_shift_buffer_depth, + 1800.0, + "Guaranteed duration of the time shifting buffer for dynamic " + "media presentations, in seconds."); +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 live profile only."); diff --git a/app/mpd_flags.h b/app/mpd_flags.h index 2021fe1f7d..ec1865c040 100644 --- a/app/mpd_flags.h +++ b/app/mpd_flags.h @@ -15,5 +15,10 @@ DECLARE_bool(output_media_info); DECLARE_string(mpd_output); DECLARE_string(scheme_id_uri); DECLARE_string(base_urls); +DECLARE_double(availability_time_offset); +DECLARE_double(minimum_update_period); +DECLARE_double(min_buffer_time); +DECLARE_double(time_shift_buffer_depth); +DECLARE_double(suggested_presentation_delay); #endif // APP_MPD_FLAGS_H_ diff --git a/app/packager_main.cc b/app/packager_main.cc index d4ed701c7d..03bc438097 100644 --- a/app/packager_main.cc +++ b/app/packager_main.cc @@ -234,6 +234,10 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) { if (!GetMuxerOptions(&muxer_options)) return false; + MpdOptions mpd_options; + if (!GetMpdOptions(&mpd_options)) + return false; + // Create encryption key source if needed. scoped_ptr encryption_key_source; if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption) { @@ -248,8 +252,7 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) { FLAGS_single_segment ? kOnDemandProfile : kLiveProfile; std::vector base_urls; base::SplitString(FLAGS_base_urls, ',', &base_urls); - // TODO(rkuroiwa,kqyang): Get mpd options from command line. - mpd_notifier.reset(new SimpleMpdNotifier(profile, MpdOptions(), base_urls, + mpd_notifier.reset(new SimpleMpdNotifier(profile, mpd_options, base_urls, FLAGS_mpd_output)); if (!mpd_notifier->Init()) { LOG(ERROR) << "MpdNotifier failed to initialize."; diff --git a/app/packager_util.cc b/app/packager_util.cc index 159dcdd395..7836131aaa 100644 --- a/app/packager_util.cc +++ b/app/packager_util.cc @@ -8,6 +8,7 @@ #include #include "app/fixed_key_encryption_flags.h" +#include "app/mpd_flags.h" #include "app/muxer_flags.h" #include "app/widevine_encryption_flags.h" #include "base/logging.h" @@ -19,9 +20,12 @@ #include "media/base/stream_info.h" #include "media/base/widevine_encryption_key_source.h" #include "media/file/file.h" +#include "mpd/base/mpd_builder.h" DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info."); +using dash_packager::MpdOptions; + namespace media { void DumpStreamInfo(const std::vector& streams) { @@ -128,6 +132,18 @@ bool GetMuxerOptions(MuxerOptions* muxer_options) { return true; } +bool GetMpdOptions(MpdOptions* mpd_options) { + DCHECK(mpd_options); + + mpd_options->availability_time_offset = FLAGS_availability_time_offset; + mpd_options->minimum_update_period = FLAGS_minimum_update_period; + mpd_options->min_buffer_time = FLAGS_min_buffer_time; + mpd_options->time_shift_buffer_depth = FLAGS_time_shift_buffer_depth; + mpd_options->suggested_presentation_delay = + FLAGS_suggested_presentation_delay; + return true; +} + MediaStream* FindFirstStreamOfType(const std::vector& streams, StreamType stream_type) { typedef std::vector::const_iterator StreamIterator; diff --git a/app/packager_util.h b/app/packager_util.h index 4845c9fee3..9bea418483 100644 --- a/app/packager_util.h +++ b/app/packager_util.h @@ -17,6 +17,10 @@ DECLARE_bool(dump_stream_info); +namespace dash_packager { +struct MpdOptions; +} + namespace media { class EncryptionKeySource; @@ -39,6 +43,9 @@ bool AssignFlagsFromProfile(); /// Fill MuxerOptions members using provided command line options. bool GetMuxerOptions(MuxerOptions* muxer_options); +/// Fill MpdOptions members using provided command line options. +bool GetMpdOptions(dash_packager::MpdOptions* mpd_options); + /// Select and add a stream from a provided set to a muxer. /// @param streams contains the set of MediaStreams from which to select. /// @param stream_selector is a string containing one of the following values: diff --git a/media/base/muxer_options.h b/media/base/muxer_options.h index fd05e37b83..5589817a44 100644 --- a/media/base/muxer_options.h +++ b/media/base/muxer_options.h @@ -7,6 +7,8 @@ #ifndef MEDIA_BASE_MUXER_OPTIONS_H_ #define MEDIA_BASE_MUXER_OPTIONS_H_ +#include "base/basictypes.h" + #include #include "base/basictypes.h" @@ -65,7 +67,8 @@ struct MuxerOptions { /// Specify temporary directory for intermediate files. std::string temp_dir; - /// User-specified bandwidth for the stream. zero means "unspecified". + /// User-specified bit rate for the media stream. If zero, the muxer will + /// attempt to estimate. uint32 bandwidth; }; diff --git a/mpd/base/mpd_builder.cc b/mpd/base/mpd_builder.cc index 024216a407..465af607ae 100644 --- a/mpd/base/mpd_builder.cc +++ b/mpd/base/mpd_builder.cc @@ -6,6 +6,7 @@ #include "mpd/base/mpd_builder.h" +#include #include #include @@ -90,18 +91,19 @@ bool Positive(double d) { } // Return current time in XML DateTime format. -std::string XmlDateTimeNow() { - base::Time now = base::Time::Now(); - base::Time::Exploded now_exploded; - now.UTCExplode(&now_exploded); +std::string XmlDateTimeNowWithOffset(int32 offset_seconds) { + base::Time time = base::Time::Now(); + time += base::TimeDelta::FromSeconds(offset_seconds); + base::Time::Exploded time_exploded; + time.UTCExplode(&time_exploded); return base::StringPrintf("%4d-%02d-%02dT%02d:%02d:%02d", - now_exploded.year, - now_exploded.month, - now_exploded.day_of_month, - now_exploded.hour, - now_exploded.minute, - now_exploded.second); + time_exploded.year, + time_exploded.month, + time_exploded.day_of_month, + time_exploded.hour, + time_exploded.minute, + time_exploded.second); } void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) { @@ -157,13 +159,12 @@ int SearchTimedOutRepeatIndex(uint64 timeshift_limit, } // namespace MpdOptions::MpdOptions() - : minimum_update_period(), - min_buffer_time(), - time_shift_buffer_depth(), - suggested_presentation_delay(), - max_segment_duration(), - max_subsegment_duration(), - number_of_blocks_for_bandwidth_estimation() {} + : availability_time_offset(0), + minimum_update_period(0), + // TODO(tinskip): Set min_buffer_time in unit tests rather than here. + min_buffer_time(2.0), + time_shift_buffer_depth(0), + suggested_presentation_delay(0) {} MpdOptions::~MpdOptions() {} @@ -222,9 +223,6 @@ xmlDocPtr MpdBuilder::GenerateMpd() { static const char kXmlVersion[] = "1.0"; xml::ScopedXmlPtr::type doc(xmlNewDoc(BAD_CAST kXmlVersion)); XmlNode mpd("MPD"); - AddMpdNameSpaceInfo(&mpd); - - SetMpdOptionsValues(&mpd); // Iterate thru AdaptationSets and add them to one big Period element. XmlNode period("Period"); @@ -254,6 +252,8 @@ xmlDocPtr MpdBuilder::GenerateMpd() { if (!mpd.AddChild(period.PassScopedPtr())) return NULL; + AddMpdNameSpaceInfo(&mpd); + AddCommonMpdInfo(&mpd); switch (type_) { case kStatic: AddStaticMpdInfo(&mpd); @@ -271,6 +271,17 @@ xmlDocPtr MpdBuilder::GenerateMpd() { return doc.release(); } +void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) { + if (Positive(mpd_options_.min_buffer_time)) { + mpd_node->SetStringAttribute( + "minBufferTime", + SecondsToXmlDuration(mpd_options_.min_buffer_time)); + } else { + LOG(ERROR) << "minBufferTime value not specified."; + // TODO(tinskip): Propagate error. + } +} + void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) { DCHECK(mpd_node); DCHECK_EQ(MpdBuilder::kStatic, type_); @@ -294,6 +305,38 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) { "urn:mpeg:dash:profile:isoff-live:2011"; mpd_node->SetStringAttribute("type", kDynamicMpdType); mpd_node->SetStringAttribute("profiles", kDynamicMpdProfile); + + // 'availabilityStartTime' is required for dynamic profile. Calculate if + // not already calculated. + if (availability_start_time_.empty()) { + double earliest_presentation_time; + if (GetEarliestTimestamp(&earliest_presentation_time)) { + availability_start_time_ = + XmlDateTimeNowWithOffset(mpd_options_.availability_time_offset + - std::ceil(earliest_presentation_time)); + } else { + LOG(ERROR) << "Could not determine the earliest segment presentation " + "time for availabilityStartTime calculation."; + // TODO(tinskip). Propagate an error. + } + } + if (!availability_start_time_.empty()) + mpd_node->SetStringAttribute("availabilityStartTime", availability_start_time_); + + if (Positive(mpd_options_.minimum_update_period)) { + mpd_node->SetStringAttribute( + "minimumUpdatePeriod", + SecondsToXmlDuration(mpd_options_.minimum_update_period)); + } else { + LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod " + "specified."; + } + + SetIfPositive( + "timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth, mpd_node); + SetIfPositive("suggestedPresentationDelay", + mpd_options_.suggested_presentation_delay, + mpd_node); } float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) { @@ -328,62 +371,25 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) { return max_duration; } -void MpdBuilder::SetMpdOptionsValues(XmlNode* mpd) { - if (type_ == kStatic) { - if (!mpd_options_.availability_start_time.empty()) { - mpd->SetStringAttribute("availabilityStartTime", - mpd_options_.availability_start_time); +bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) { + DCHECK(timestamp_seconds); + + double earliest_timestamp(-1); + for (std::list::const_iterator iter = + adaptation_sets_.begin(); + iter != adaptation_sets_.end(); + ++iter) { + double timestamp; + if ((*iter)->GetEarliestTimestamp(×tamp) && + ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) { + earliest_timestamp = timestamp; } - LOG_IF(WARNING, Positive(mpd_options_.minimum_update_period)) - << "minimumUpdatePeriod should not be present in 'static' profile. " - "Ignoring."; - LOG_IF(WARNING, Positive(mpd_options_.time_shift_buffer_depth)) - << "timeShiftBufferDepth will not be used for 'static' profile. " - "Ignoring."; - LOG_IF(WARNING, Positive(mpd_options_.suggested_presentation_delay)) - << "suggestedPresentationDelay will not be used for 'static' profile. " - "Ignoring."; - } else if (type_ == kDynamic) { - // 'availabilityStartTime' is required for dynamic profile, so use current - // time if not specified. - const std::string avail_start = - !mpd_options_.availability_start_time.empty() - ? mpd_options_.availability_start_time - : XmlDateTimeNow(); - mpd->SetStringAttribute("availabilityStartTime", avail_start); - - if (Positive(mpd_options_.minimum_update_period)) { - mpd->SetStringAttribute( - "minimumUpdatePeriod", - SecondsToXmlDuration(mpd_options_.minimum_update_period)); - } else { - // TODO(rkuroiwa): Set minimumUpdatePeriod to some default value. - LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod " - "specified. Setting minimumUpdatePeriod to 0."; - } - - SetIfPositive( - "timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth, mpd); - SetIfPositive("suggestedPresentationDelay", - mpd_options_.suggested_presentation_delay, - mpd); } + if (earliest_timestamp < 0) + return false; - const double kDefaultMinBufferTime = 2.0; - const double min_buffer_time = Positive(mpd_options_.min_buffer_time) - ? mpd_options_.min_buffer_time - : kDefaultMinBufferTime; - mpd->SetStringAttribute("minBufferTime", - SecondsToXmlDuration(min_buffer_time)); - - if (!mpd_options_.availability_end_time.empty()) { - mpd->SetStringAttribute("availabilityEndTime", - mpd_options_.availability_end_time); - } - - SetIfPositive("maxSegmentDuration", mpd_options_.max_segment_duration, mpd); - SetIfPositive( - "maxSubsegmentDuration", mpd_options_.max_subsegment_duration, mpd); + *timestamp_seconds = earliest_timestamp; + return true; } AdaptationSet::AdaptationSet(uint32 adaptation_set_id, @@ -441,6 +447,29 @@ xml::ScopedXmlPtr::type AdaptationSet::GetXml() { return adaptation_set.PassScopedPtr(); } +bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) { + DCHECK(timestamp_seconds); + + base::AutoLock scoped_lock(lock_); + double earliest_timestamp(-1); + for (std::list::const_iterator iter = + representations_.begin(); + iter != representations_.end(); + ++iter) { + double timestamp; + if ((*iter)->GetEarliestTimestamp(×tamp) && + ((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) { + earliest_timestamp = timestamp; + } + } + if (earliest_timestamp < 0) + return false; + + *timestamp_seconds = earliest_timestamp; + return true; +} + + Representation::Representation(const MediaInfo& media_info, const MpdOptions& mpd_options, uint32 id) @@ -715,4 +744,17 @@ std::string Representation::GetAudioMimeType() const { return GetMimeType("audio", media_info_.container_type()); } +bool Representation::GetEarliestTimestamp(double* timestamp_seconds) { + DCHECK(timestamp_seconds); + + base::AutoLock scoped_lock(lock_); + if (segment_infos_.empty()) + return false; + + *timestamp_seconds = + static_cast(segment_infos_.begin()->start_time) / + GetTimeScale(media_info_); + return true; +} + } // namespace dash_packager diff --git a/mpd/base/mpd_builder.h b/mpd/base/mpd_builder.h index 3cab823813..cbeebb6cce 100644 --- a/mpd/base/mpd_builder.h +++ b/mpd/base/mpd_builder.h @@ -41,18 +41,11 @@ struct MpdOptions { MpdOptions(); ~MpdOptions(); - std::string availability_start_time; - std::string availability_end_time; + double availability_time_offset; double minimum_update_period; double min_buffer_time; double time_shift_buffer_depth; double suggested_presentation_delay; - double max_segment_duration; - double max_subsegment_duration; - - /// Value passed to BandwidthEstimator's contructor. See BandwidthEstimator - /// for more. - int number_of_blocks_for_bandwidth_estimation; }; /// This class generates DASH MPDs (Media Presentation Descriptions). @@ -86,8 +79,8 @@ class MpdBuilder { MpdType type() { return type_; } private: - // DynamicMpdBuilderTest uses SetMpdOptionsValues to set availabilityStartTime - // so that the test doesn't need to depend on current time. + // DynamicMpdBuilderTest needs to set availabilityStartTime so that the test + // doesn't need to depend on current time. friend class DynamicMpdBuilderTest; bool ToStringImpl(std::string* output); @@ -97,6 +90,10 @@ class MpdBuilder { // On failure, this returns NULL. xmlDocPtr GenerateMpd(); + // Set MPD attributes common to all profiles. Uses non-zero |mpd_options_| to + // set attributes for the MPD. + void AddCommonMpdInfo(xml::XmlNode* mpd_node); + // Adds 'static' MPD attributes and elements to |mpd_node|. This assumes that // the first child element is a Period element. void AddStaticMpdInfo(xml::XmlNode* mpd_node); @@ -106,10 +103,13 @@ class MpdBuilder { float GetStaticMpdDuration(xml::XmlNode* mpd_node); - // Use |mpd_options_| to set attributes for MPD. Only values that are set will be - // used, i.e. if a string field is not empty and numeric field is not 0. - // Required fields will be set with some reasonable values. - void SetMpdOptionsValues(xml::XmlNode* mpd_node); + // Set MPD attributes for dynamic profile MPD. Uses non-zero |mpd_options_| as + // well as various calculations to set attributes for the MPD. + void SetDynamicMpdAttributes(xml::XmlNode* mpd_node); + + // Gets the earliest, normalized segment timestamp. Returns true if + // successful, false otherwise. + bool GetEarliestTimestamp(double* timestamp_seconds); MpdType type_; MpdOptions mpd_options_; @@ -117,6 +117,7 @@ class MpdBuilder { ::STLElementDeleter > adaptation_sets_deleter_; std::list base_urls_; + std::string availability_start_time_; base::Lock lock_; base::AtomicSequenceNumber adaptation_set_counter_; @@ -167,6 +168,10 @@ class AdaptationSet { const MpdOptions& mpd_options_, base::AtomicSequenceNumber* representation_counter); + // Gets the earliest, normalized segment timestamp. Returns true if + // successful, false otherwise. + bool GetEarliestTimestamp(double* timestamp_seconds); + std::list content_protection_elements_; std::list representations_; ::STLElementDeleter > representations_deleter_; @@ -248,6 +253,10 @@ class Representation { std::string GetVideoMimeType() const; std::string GetAudioMimeType() const; + // Gets the earliest, normalized segment timestamp. Returns true if + // successful, false otherwise. + bool GetEarliestTimestamp(double* timestamp_seconds); + MediaInfo media_info_; std::list content_protection_elements_; std::list segment_infos_; @@ -258,7 +267,7 @@ class Representation { std::string mime_type_; std::string codecs_; BandwidthEstimator bandwidth_estimator_; - const MpdOptions& mpd_options_;; + const MpdOptions& mpd_options_; // startNumber attribute for SegmentTemplate. // Starts from 1. diff --git a/mpd/base/mpd_builder_unittest.cc b/mpd/base/mpd_builder_unittest.cc index 5e86a96eaf..af1909eb87 100644 --- a/mpd/base/mpd_builder_unittest.cc +++ b/mpd/base/mpd_builder_unittest.cc @@ -99,7 +99,7 @@ class DynamicMpdBuilderTest : public MpdBuilderTest { // Anchors availabilityStartTime so that the test result doesn't depend on the // current time. virtual void SetUp() { - mpd_.mpd_options_.availability_start_time = "2011-12-25T12:30:00"; + mpd_.availability_start_time_ = "2011-12-25T12:30:00"; } MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; } @@ -370,9 +370,9 @@ TEST_F(DynamicMpdBuilderTest, CheckMpdAttributes) { "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\" availabilityStartTime=\"2011-12-25T12:30:00\" " - "minBufferTime=\"PT2S\" type=\"dynamic\" " - "profiles=\"urn:mpeg:dash:profile:isoff-live:2011\">\n" + "DASH-MPD.xsd\" minBufferTime=\"PT2S\" type=\"dynamic\" " + "profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" " + "availabilityStartTime=\"2011-12-25T12:30:00\">\n" " \n" "\n";