shaka-packager/packager/mpd/base/dash_iop_mpd_notifier.cc

314 lines
11 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/base/stl_util.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 {
// 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(const MpdOptions& mpd_options)
: MpdNotifier(mpd_options),
output_path_(mpd_options.mpd_params.mpd_output),
mpd_builder_(new MpdBuilder(mpd_options)) {
for (const std::string& base_url : mpd_options.mpd_params.base_urls)
mpd_builder_->AddBaseUrl(base_url);
}
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;
SetAdaptationSetSwitching(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::ReuseAdaptationSet(
const std::list<AdaptationSet*>& adaptation_sets,
const MediaInfo& media_info) {
const bool has_protected_content = media_info.has_protected_content();
for (AdaptationSet* adaptation_set : adaptation_sets) {
ProtectedContentMap::const_iterator protected_content_it =
protected_content_map_.find(adaptation_set->id());
// If the AdaptationSet ID is not registered in the map, then it is clear
// content.
if (protected_content_it == protected_content_map_.end()) {
// Can reuse the AdaptationSet without content protection.
if (!has_protected_content) {
return adaptation_set;
}
continue;
}
if (ProtectedContentEq(protected_content_it->second,
media_info.protected_content())) {
// Content protection info matches. Reuse the AdaptationSet.
return adaptation_set;
}
}
return nullptr;
}
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);
AdaptationSet* reuse_adaptation_set =
ReuseAdaptationSet(adaptation_sets, media_info);
if (reuse_adaptation_set)
return reuse_adaptation_set;
// 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 are switchable.
void DashIopMpdNotifier::SetAdaptationSetSwitching(
const std::string& key,
AdaptationSet* adaptation_set) {
// This adaptation set is already visited.
if (!adaptation_set->adaptation_set_switching_ids().empty())
return;
ProtectedContentMap::const_iterator protected_content_it =
protected_content_map_.find(adaptation_set->id());
// Clear contents should be in one AdaptationSet and may not be switchable
// with encrypted contents.
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, may not be switchable.
continue;
}
if (adaptation_set_uuids == GetUUIDs(loop_protected_content)) {
AdaptationSet& uuid_match_adaptation_set = **adaptation_set_it;
// They match. These AdaptationSets are switchable.
uuid_match_adaptation_set.AddAdaptationSetSwitching(adaptation_set->id());
adaptation_set->AddAdaptationSetSwitching(uuid_match_adaptation_set.id());
}
}
}
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);
}
if (media_info.video_info().has_playback_rate()) {
uint32_t trick_play_reference_id = 0;
if (!FindOriginalAdaptationSetForTrickPlay(media_info,
&trick_play_reference_id)) {
LOG(ERROR) << "Failed to find main adaptation set for trick play.";
return nullptr;
}
DCHECK_NE(new_adaptation_set->id(), trick_play_reference_id);
new_adaptation_set->AddTrickPlayReferenceId(trick_play_reference_id);
}
}
return new_adaptation_set;
}
bool DashIopMpdNotifier::FindOriginalAdaptationSetForTrickPlay(
const MediaInfo& media_info,
uint32_t* main_adaptation_set_id) {
MediaInfo media_info_no_trickplay = media_info;
media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
std::string key = GetAdaptationSetKey(media_info_no_trickplay);
const std::list<AdaptationSet*>& adaptation_sets =
adaptation_set_list_map_[key];
if (adaptation_sets.empty()) {
return false;
}
AdaptationSet* reuse_adaptation_set =
ReuseAdaptationSet(adaptation_sets, media_info);
if (!reuse_adaptation_set) {
return false;
}
*main_adaptation_set_id = reuse_adaptation_set->id();
return true;
}
} // namespace shaka