286 lines
10 KiB
C++
286 lines
10 KiB
C++
// 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<std::string> GetUUIDs(
|
|
const MediaInfo::ProtectedContent& protected_content) {
|
|
std::set<std::string> 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<std::string>& 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<uint8_t>& new_key_id,
|
|
const std::vector<uint8_t>& 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
|
|
// <ContentProtection> 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<AdaptationSet*>& 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<AdaptationSet*>::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 <ContentProtection> 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<std::string> adaptation_set_uuids =
|
|
GetUUIDs(protected_content_it->second);
|
|
|
|
std::list<AdaptationSet*>& 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<AdaptationSet*>::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<int>(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<AdaptationSet*>* 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
|