// Copyright 2015 Google Inc. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd #include "packager/mpd/base/dash_iop_mpd_notifier.h" #include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/mpd_notifier_util.h" #include "packager/mpd/base/mpd_utils.h" namespace shaka { namespace { const int kStartingGroupId = 1; // The easiest way to check whether two protobufs are equal, is to compare the // serialized version. bool ProtectedContentEq( const MediaInfo::ProtectedContent& content_protection1, const MediaInfo::ProtectedContent& content_protection2) { return content_protection1.SerializeAsString() == content_protection2.SerializeAsString(); } std::set GetUUIDs( const MediaInfo::ProtectedContent& protected_content) { std::set uuids; for (int i = 0; i < protected_content.content_protection_entry().size(); ++i) { const MediaInfo::ProtectedContent::ContentProtectionEntry& entry = protected_content.content_protection_entry(i); uuids.insert(entry.uuid()); } return uuids; } } // namespace DashIopMpdNotifier::DashIopMpdNotifier( DashProfile dash_profile, const MpdOptions& mpd_options, const std::vector& base_urls, const std::string& output_path) : MpdNotifier(dash_profile), output_path_(output_path), mpd_builder_(new MpdBuilder(dash_profile == kLiveProfile ? MpdBuilder::kDynamic : MpdBuilder::kStatic, mpd_options)), next_group_id_(kStartingGroupId) { DCHECK(dash_profile == kLiveProfile || dash_profile == kOnDemandProfile); for (size_t i = 0; i < base_urls.size(); ++i) mpd_builder_->AddBaseUrl(base_urls[i]); } DashIopMpdNotifier::~DashIopMpdNotifier() {} bool DashIopMpdNotifier::Init() { return true; } bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info, uint32_t* container_id) { DCHECK(container_id); ContentType content_type = GetContentType(media_info); if (content_type == kContentTypeUnknown) return false; base::AutoLock auto_lock(lock_); const std::string key = GetAdaptationSetKey(media_info); AdaptationSet* adaptation_set = GetAdaptationSetForMediaInfo(key, media_info); DCHECK(adaptation_set); if (media_info.has_text_info()) { // IOP requires all AdaptationSets to have (sub)segmentAlignment set to // true, so carelessly set it to true. // In practice it doesn't really make sense to adapt between text tracks. adaptation_set->ForceSetSegmentAlignment(true); } MediaInfo adjusted_media_info(media_info); MpdBuilder::MakePathsRelativeToMpd(output_path_, &adjusted_media_info); Representation* representation = adaptation_set->AddRepresentation(adjusted_media_info); if (!representation) return false; representation_id_to_adaptation_set_[representation->id()] = adaptation_set; SetGroupId(key, adaptation_set); *container_id = representation->id(); DCHECK(!ContainsKey(representation_map_, representation->id())); representation_map_[representation->id()] = representation; return true; } bool DashIopMpdNotifier::NotifySampleDuration(uint32_t container_id, uint32_t sample_duration) { base::AutoLock auto_lock(lock_); RepresentationMap::iterator it = representation_map_.find(container_id); if (it == representation_map_.end()) { LOG(ERROR) << "Unexpected container_id: " << container_id; return false; } it->second->SetSampleDuration(sample_duration); return true; } bool DashIopMpdNotifier::NotifyNewSegment(uint32_t container_id, uint64_t start_time, uint64_t duration, uint64_t size) { base::AutoLock auto_lock(lock_); RepresentationMap::iterator it = representation_map_.find(container_id); if (it == representation_map_.end()) { LOG(ERROR) << "Unexpected container_id: " << container_id; return false; } it->second->AddNewSegment(start_time, duration, size); return true; } bool DashIopMpdNotifier::NotifyEncryptionUpdate( uint32_t container_id, const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh) { base::AutoLock auto_lock(lock_); RepresentationMap::iterator it = representation_map_.find(container_id); if (it == representation_map_.end()) { LOG(ERROR) << "Unexpected container_id: " << container_id; return false; } AdaptationSet* adaptation_set_for_representation = representation_id_to_adaptation_set_[it->second->id()]; adaptation_set_for_representation->UpdateContentProtectionPssh( drm_uuid, Uint8VectorToBase64(new_pssh)); return true; } bool DashIopMpdNotifier::AddContentProtectionElement( uint32_t container_id, const ContentProtectionElement& content_protection_element) { // Intentionally not implemented because if a Representation gets a new // element, then it might require moving the // Representation out of the AdaptationSet. There's no logic to do that // yet. return false; } bool DashIopMpdNotifier::Flush() { base::AutoLock auto_lock(lock_); return WriteMpdToFile(output_path_, mpd_builder_.get()); } AdaptationSet* DashIopMpdNotifier::GetAdaptationSetForMediaInfo( const std::string& key, const MediaInfo& media_info) { std::list& adaptation_sets = adaptation_set_list_map_[key]; if (adaptation_sets.empty()) return NewAdaptationSet(media_info, &adaptation_sets); const bool has_protected_content = media_info.has_protected_content(); for (std::list::const_iterator adaptation_set_it = adaptation_sets.begin(); adaptation_set_it != adaptation_sets.end(); ++adaptation_set_it) { ProtectedContentMap::const_iterator protected_content_it = protected_content_map_.find((*adaptation_set_it)->id()); // If the AdaptationSet ID is not registered in the map, then it is clear // content (or encrypted but doesn't need element // possibly because the player knows how to handle it). if (protected_content_it == protected_content_map_.end()) { // Can reuse the AdaptationSet without content protection. if (!has_protected_content) return *adaptation_set_it; continue; } if (ProtectedContentEq(protected_content_it->second, media_info.protected_content())) { // Content protection info matches. Reuse the AdaptationSet. return *adaptation_set_it; } } // None of the adaptation sets match with the new content protection. // Need a new one. return NewAdaptationSet(media_info, &adaptation_sets); } // Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the // same UUIDs then those should be groupable. void DashIopMpdNotifier::SetGroupId(const std::string& key, AdaptationSet* adaptation_set) { if (adaptation_set->Group() >= 0) // @group already assigned. return; ProtectedContentMap::const_iterator protected_content_it = protected_content_map_.find(adaptation_set->id()); // Clear contents should be in one AdaptationSet, so no need to assign // @group. if (protected_content_it == protected_content_map_.end()) { DVLOG(1) << "No content protection set for AdaptationSet@id=" << adaptation_set->id(); return; } // Get all the UUIDs of the ContentProtections in AdaptationSet. std::set adaptation_set_uuids = GetUUIDs(protected_content_it->second); std::list& same_type_adapatation_sets = adaptation_set_list_map_[key]; DCHECK(!same_type_adapatation_sets.empty()) << "same_type_adapatation_sets should not be null, it should at least " "contain adaptation_set"; for (std::list::iterator adaptation_set_it = same_type_adapatation_sets.begin(); adaptation_set_it != same_type_adapatation_sets.end(); ++adaptation_set_it) { const uint32_t loop_adaptation_set_id = (*adaptation_set_it)->id(); if (loop_adaptation_set_id == adaptation_set->id() || !ContainsKey(protected_content_map_, loop_adaptation_set_id)) { continue; } const MediaInfo::ProtectedContent& loop_protected_content = protected_content_map_[loop_adaptation_set_id]; if (static_cast(adaptation_set_uuids.size()) != loop_protected_content.content_protection_entry().size()) { // Different number of UUIDs, cannot be grouped. continue; } if (adaptation_set_uuids == GetUUIDs(loop_protected_content)) { AdaptationSet& uuid_match_adaptation_set = **adaptation_set_it; // They match. These AdaptationSets can be in the same group. Break out. if (uuid_match_adaptation_set.Group() >= 0) { adaptation_set->SetGroup(uuid_match_adaptation_set.Group()); } else { const int group_id = next_group_id_++; uuid_match_adaptation_set.SetGroup(group_id); adaptation_set->SetGroup(group_id); } break; } } } AdaptationSet* DashIopMpdNotifier::NewAdaptationSet( const MediaInfo& media_info, std::list* adaptation_sets) { std::string language = GetLanguage(media_info); AdaptationSet* new_adaptation_set = mpd_builder_->AddAdaptationSet(language); if (media_info.has_protected_content()) { DCHECK(!ContainsKey(protected_content_map_, new_adaptation_set->id())); protected_content_map_[new_adaptation_set->id()] = media_info.protected_content(); AddContentProtectionElements(media_info, new_adaptation_set); } adaptation_sets->push_back(new_adaptation_set); if (media_info.has_video_info()) { // Because 'lang' is ignored for videos, |adaptation_sets| must have // all the video AdaptationSets. if (adaptation_sets->size() > 2) { new_adaptation_set->AddRole(AdaptationSet::kRoleMain); } else if (adaptation_sets->size() == 2) { // Set "main" Role for both AdaptatoinSets. (*adaptation_sets->begin())->AddRole(AdaptationSet::kRoleMain); new_adaptation_set->AddRole(AdaptationSet::kRoleMain); } } return new_adaptation_set; } } // namespace shaka