diff --git a/mpd/base/mpd_builder.cc b/mpd/base/mpd_builder.cc index 549b2c816b..dbd54a0685 100644 --- a/mpd/base/mpd_builder.cc +++ b/mpd/base/mpd_builder.cc @@ -19,6 +19,27 @@ using xml::AdaptationSetXmlNode; namespace { +std::string GetMimeType( + const std::string& prefix, + MediaInfo::ContainerType container_type) { + switch (container_type) { + case MediaInfo::CONTAINER_MP4: + return prefix + "/mp4"; + case MediaInfo::CONTAINER_MPEG2_TS: + // TODO(rkuroiwa): Find out whether uppercase or lowercase should be used + // for mp2t. DASH MPD spec uses lowercase but RFC3555 says uppercase. + return prefix + "/MP2T"; + case MediaInfo::CONTAINER_WEBM: + return prefix + "/webm"; + default: + break; + } + + // Unsupported container types should be rejected/handled by the caller. + NOTREACHED() << "Unrecognized container type: " << container_type; + return std::string(); +} + void AddMpdNameSpaceInfo(XmlNode* mpd) { DCHECK(mpd); @@ -101,8 +122,8 @@ xmlDocPtr MpdBuilder::GenerateMpd() { XmlNode mpd("MPD"); AddMpdNameSpaceInfo(&mpd); - // Currently set to 2. Does this need calculation? - const int kMinBufferTime = 2; + // TODO(rkuroiwa): Currently set to 2. Does this need calculation? + const float kMinBufferTime = 2.0f; mpd.SetStringAttribute("minBufferTime", SecondsToXmlDuration(kMinBufferTime)); // Iterate thru AdaptationSets and add them to one big Period element. @@ -159,7 +180,7 @@ void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) { SecondsToXmlDuration(GetStaticMpdDuration(mpd_node))); } -uint32 MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) { +float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) { DCHECK(mpd_node); DCHECK_EQ(MpdBuilder::kStatic, type_); @@ -168,19 +189,17 @@ uint32 MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) { DCHECK_EQ(strcmp(reinterpret_cast(period_node->name), "Period"), 0); - // TODO(rkuroiwa): Update this so that the duration for each Representation is - // (duration / timescale). // Attribute mediaPresentationDuration must be present for 'static' MPD. So - // setting "P0S" is still required if none of the representaions had a - // duration attribute. - uint32 max_duration = 0; + // setting "PT0S" is required even if none of the representaions have duration + // attribute. + float max_duration = 0.0f; for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node); adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) { for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set); representation; representation = xmlNextElementSibling(representation)) { - uint32 duration = 0; + float duration = 0.0f; if (GetDurationAttribute(representation, &duration)) { max_duration = max_duration > duration ? max_duration : duration; @@ -206,20 +225,12 @@ AdaptationSet::~AdaptationSet() {} Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) { base::AutoLock scoped_lock(lock_); - if (HasVODOnlyFields(media_info) && HasLiveOnlyFields(media_info)) { - LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields."; - return NULL; - } - - if (!media_info.has_bandwidth()) { - LOG(ERROR) << "MediaInfo missing required bandwidth field."; - return NULL; - } - scoped_ptr representation( new Representation(media_info, representation_counter_->GetNext())); - DCHECK(representation); + if (!representation->Init()) + return NULL; + representations_.push_back(representation.get()); return representation.release(); } @@ -260,6 +271,43 @@ Representation::Representation(const MediaInfo& media_info, uint32 id) Representation::~Representation() {} +bool Representation::Init() { + if (!HasRequiredMediaInfoFields()) + return false; + + codecs_ = GetCodecs(media_info_); + if (codecs_.empty()) { + LOG(ERROR) << "Missing codec info in MediaInfo."; + return false; + } + + const bool has_video_info = media_info_.video_info_size() > 0; + const bool has_audio_info = media_info_.audio_info_size() > 0; + + if (!has_video_info && !has_audio_info) { + // TODO(rkuroiwa): Allow text input. + LOG(ERROR) << "Representation needs video or audio."; + return false; + } + + if (!media_info_.container_type() == MediaInfo::CONTAINER_UNKNOWN) { + // TODO(rkuroiwa): This might not be the right behavior. Maybe somehow + // infer from something else like media file name? + LOG(ERROR) << "'container_type' in MediaInfo cannot be CONTAINER_UNKNOWN."; + return false; + } + + // Check video and then audio. Usually when there is audio + video, we take + // video/. + if (has_video_info) { + mime_type_ = GetVideoMimeType(); + } else if (has_audio_info) { + mime_type_ = GetAudioMimeType(); + } + + return true; +} + void Representation::AddContentProtectionElement( const ContentProtectionElement& content_protection_element) { base::AutoLock scoped_lock(lock_); @@ -289,33 +337,33 @@ xml::ScopedXmlPtr::type Representation::GetXml() { return xml::ScopedXmlPtr::type(); } - // Two 'Mandatory' fields for Representation. + // Mandatory fields for Representation. representation.SetId(id_); representation.SetIntegerAttribute("bandwidth", media_info_.bandwidth()); + representation.SetStringAttribute("codecs", codecs_); + representation.SetStringAttribute("mimeType", mime_type_); const bool has_video_info = media_info_.video_info_size() > 0; const bool has_audio_info = media_info_.audio_info_size() > 0; - if (has_video_info || has_audio_info) { - const std::string codecs = GetCodecs(media_info_); - if (!codecs.empty()) - representation.SetStringAttribute("codecs", codecs); - if (has_video_info) { - if (!representation.AddVideoInfo(media_info_.video_info())) - return xml::ScopedXmlPtr::type(); - } + if (has_video_info && + !representation.AddVideoInfo(media_info_.video_info())) { + LOG(ERROR) << "Failed to add video info to Representation XML."; + return xml::ScopedXmlPtr::type(); + } - if (has_audio_info) { - if (!representation.AddAudioInfo(media_info_.audio_info())) - return xml::ScopedXmlPtr::type(); - } + if (has_audio_info && + !representation.AddAudioInfo(media_info_.audio_info())) { + LOG(ERROR) << "Failed to add audio info to Representation XML."; + return xml::ScopedXmlPtr::type(); } // TODO(rkuroiwa): Add TextInfo. // TODO(rkuroiwa): Add ContentProtection info. - if (HasVODOnlyFields(media_info_)) { - if (!representation.AddVODOnlyInfo(media_info_)) - return xml::ScopedXmlPtr::type(); + if (HasVODOnlyFields(media_info_) && + !representation.AddVODOnlyInfo(media_info_)) { + LOG(ERROR) << "Failed to add VOD info."; + return xml::ScopedXmlPtr::type(); } // TODO(rkuroiwa): Handle Live case. Handle data in @@ -323,4 +371,31 @@ xml::ScopedXmlPtr::type Representation::GetXml() { return representation.PassScopedPtr(); } +bool Representation::HasRequiredMediaInfoFields() { + if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) { + LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields."; + return false; + } + + if (!media_info_.has_bandwidth()) { + LOG(ERROR) << "MediaInfo missing required field: bandwidth."; + return false; + } + + if (!media_info_.has_container_type()) { + LOG(ERROR) << "MediaInfo missing required field: container_type."; + return false; + } + + return true; +} + +std::string Representation::GetVideoMimeType() const { + return GetMimeType("video", media_info_.container_type()); +} + +std::string Representation::GetAudioMimeType() const { + return GetMimeType("audio", media_info_.container_type()); +} + } // namespace dash_packager diff --git a/mpd/base/mpd_builder.h b/mpd/base/mpd_builder.h index 61e7caa65b..4d297dd5e0 100644 --- a/mpd/base/mpd_builder.h +++ b/mpd/base/mpd_builder.h @@ -63,7 +63,7 @@ class MpdBuilder { // 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); - uint32 GetStaticMpdDuration(xml::XmlNode* mpd_node); + float GetStaticMpdDuration(xml::XmlNode* mpd_node); MpdType type_; std::list adaptation_sets_; @@ -122,6 +122,8 @@ class Representation { Representation(const MediaInfo& media_info, uint32 representation_id); ~Representation(); + bool Init(); + // If |element| has {value, schemeIdUri} set and has // {“value”, “schemeIdUri”} as key for additional_attributes, // then the former is used. @@ -140,6 +142,15 @@ class Representation { } private: + // Returns whether |media_info_| has required fields to generate a valid + // Representation. Returns true on success, otherwise returns false. + bool HasRequiredMediaInfoFields(); + + // Note: Because 'mimeType' is a required field for a valid MPD, these return + // strings. + std::string GetVideoMimeType() const; + std::string GetAudioMimeType() const; + MediaInfo media_info_; std::list content_protection_elements_; std::list > segment_starttime_duration_pairs_; @@ -147,6 +158,8 @@ class Representation { base::Lock lock_; const uint32 id_; + std::string mime_type_; + std::string codecs_; DISALLOW_COPY_AND_ASSIGN(Representation); }; diff --git a/mpd/base/mpd_utils.cc b/mpd/base/mpd_utils.cc index 6bd8b05d4d..351f878fb2 100644 --- a/mpd/base/mpd_utils.cc +++ b/mpd/base/mpd_utils.cc @@ -77,11 +77,11 @@ std::string GetCodecs(const MediaInfo& media_info) { return ""; } -std::string SecondsToXmlDuration(uint32 seconds) { - return "PT" + base::UintToString(seconds) + "S"; +std::string SecondsToXmlDuration(float seconds) { + return "PT" + base::DoubleToString(seconds) + "S"; } -bool GetDurationAttribute(xmlNodePtr node, uint32* duration) { +bool GetDurationAttribute(xmlNodePtr node, float* duration) { DCHECK(node); DCHECK(duration); static const char kDuration[] = "duration"; @@ -91,8 +91,14 @@ bool GetDurationAttribute(xmlNodePtr node, uint32* duration) { if (!duration_value) return false; - return base::StringToUint(reinterpret_cast(duration_value.get()), - duration); + double duration_double_precision = 0.0; + if (!base::StringToDouble(reinterpret_cast(duration_value.get()), + &duration_double_precision)) { + return false; + } + + *duration = static_cast(duration_double_precision); + return true; } } // namespace dash_packager diff --git a/mpd/base/mpd_utils.h b/mpd/base/mpd_utils.h index fb408b84b0..7035024faa 100644 --- a/mpd/base/mpd_utils.h +++ b/mpd/base/mpd_utils.h @@ -26,12 +26,10 @@ void RemoveDuplicateAttributes( // comma. std::string GetCodecs(const MediaInfo& media_info); -// TODO(rkuroiwa): This probably needs to change to floating point number. -// Returns "PS". -std::string SecondsToXmlDuration(uint32 seconds); +std::string SecondsToXmlDuration(float seconds); // Tries to get "duration" attribute from |node|. On success |duration| is set. -bool GetDurationAttribute(xmlNodePtr node, uint32* duration); +bool GetDurationAttribute(xmlNodePtr node, float* duration); } // namespace dash_packager diff --git a/mpd/base/xml/xml_node.cc b/mpd/base/xml/xml_node.cc index ae3094d142..3efe98bcf8 100644 --- a/mpd/base/xml/xml_node.cc +++ b/mpd/base/xml/xml_node.cc @@ -180,8 +180,6 @@ bool RepresentationXmlNode::AddAudioInfo( return true; } -// TODO(rkuroiwa): Add 'mimeType' field to this element. Maybe MPD builder -// classes should do this or maybe in this class. bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) { const bool need_segment_base = media_info.has_index_range() || media_info.has_init_range() ||