Add MpdNotifier that tries to generate IOP compliant MPD
- Add DashIopMpdNotifier for generating DASH-IF IOP v3 compliant MPD. Change-Id: I201b4cdafde6bb963f74d4bbaee3fecc432cb9d7
This commit is contained in:
parent
aa6f60f9fc
commit
c48a94d60f
|
@ -0,0 +1,254 @@
|
|||
// 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"
|
||||
|
||||
namespace edash_packager {
|
||||
|
||||
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) {
|
||||
std::string s1;
|
||||
std::string s2;
|
||||
return content_protection1.SerializeToString(&s1) &&
|
||||
content_protection2.SerializeToString(&s2) &&
|
||||
s1 == s2;
|
||||
}
|
||||
|
||||
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_);
|
||||
std::string lang;
|
||||
if (media_info.has_audio_info()) {
|
||||
lang = media_info.audio_info().language();
|
||||
}
|
||||
|
||||
AdaptationSet* adaptation_set =
|
||||
GetAdaptationSetForMediaInfo(media_info, content_type, lang);
|
||||
DCHECK(adaptation_set);
|
||||
|
||||
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;
|
||||
|
||||
SetGroupId(content_type, lang, adaptation_set);
|
||||
|
||||
*container_id = representation->id();
|
||||
DCHECK(!ContainsKey(representation_map_, representation->id()));
|
||||
representation_map_[representation->id()] = representation;
|
||||
|
||||
if (mpd_builder_->type() == MpdBuilder::kStatic)
|
||||
return WriteMpdToFile(output_path_, mpd_builder_.get());
|
||||
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 WriteMpdToFile(output_path_, mpd_builder_.get());
|
||||
}
|
||||
|
||||
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 WriteMpdToFile(output_path_, mpd_builder_.get());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
AdaptationSet* DashIopMpdNotifier::GetAdaptationSetForMediaInfo(
|
||||
const MediaInfo& media_info,
|
||||
ContentType content_type,
|
||||
const std::string& language) {
|
||||
std::list<AdaptationSet*>& adaptation_sets =
|
||||
adaptation_set_list_map_[content_type][language];
|
||||
if (adaptation_sets.empty())
|
||||
return NewAdaptationSet(media_info, language, &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, language, &adaptation_sets);
|
||||
}
|
||||
|
||||
// Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the
|
||||
// same UUIDs then those should be groupable.
|
||||
void DashIopMpdNotifier::SetGroupId(ContentType type,
|
||||
const std::string& language,
|
||||
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_[type][language];
|
||||
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,
|
||||
const std::string& language,
|
||||
std::list<AdaptationSet*>* adaptation_sets) {
|
||||
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);
|
||||
return new_adaptation_set;
|
||||
}
|
||||
|
||||
} // namespace edash_packager
|
|
@ -0,0 +1,113 @@
|
|||
// 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
|
||||
|
||||
#ifndef MPD_BASE_DASH_IOP_MPD_NOTIFIER_H_
|
||||
#define MPD_BASE_DASH_IOP_MPD_NOTIFIER_H_
|
||||
|
||||
#include "packager/mpd/base/mpd_notifier.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "packager/mpd/base/mpd_builder.h"
|
||||
#include "packager/mpd/base/mpd_notifier_util.h"
|
||||
#include "packager/mpd/base/mpd_options.h"
|
||||
|
||||
namespace edash_packager {
|
||||
|
||||
/// This class is an MpdNotifier which will try its best to generate a
|
||||
/// DASH IF IOPv3 compliant MPD.
|
||||
/// For example, all <ContentProtection> elements must be right under
|
||||
/// <AdaptationSet> and cannot be under <Representation>.
|
||||
class DashIopMpdNotifier : public MpdNotifier {
|
||||
public:
|
||||
DashIopMpdNotifier(DashProfile dash_profile,
|
||||
const MpdOptions& mpd_options,
|
||||
const std::vector<std::string>& base_urls,
|
||||
const std::string& output_path);
|
||||
virtual ~DashIopMpdNotifier() OVERRIDE;
|
||||
|
||||
/// @name MpdNotifier implemetation overrides.
|
||||
/// @{
|
||||
virtual bool Init() OVERRIDE;
|
||||
virtual bool NotifyNewContainer(const MediaInfo& media_info,
|
||||
uint32_t* id) OVERRIDE;
|
||||
virtual bool NotifySampleDuration(uint32_t container_id,
|
||||
uint32_t sample_duration) OVERRIDE;
|
||||
virtual bool NotifyNewSegment(uint32_t id,
|
||||
uint64_t start_time,
|
||||
uint64_t duration,
|
||||
uint64_t size) OVERRIDE;
|
||||
virtual bool AddContentProtectionElement(
|
||||
uint32_t id,
|
||||
const ContentProtectionElement& content_protection_element) OVERRIDE;
|
||||
/// @}
|
||||
|
||||
private:
|
||||
friend class DashIopMpdNotifierTest;
|
||||
|
||||
// Maps representation ID to Representation.
|
||||
typedef std::map<uint32_t, Representation*> RepresentationMap;
|
||||
|
||||
// Maps AdaptationSet ID to ProtectedContent.
|
||||
typedef std::map<uint32_t, MediaInfo::ProtectedContent> ProtectedContentMap;
|
||||
|
||||
// Checks the protected_content field of media_info and returns a non-null
|
||||
// AdaptationSet* for a new Representation.
|
||||
// This does not necessarily return a new AdaptationSet. If
|
||||
// media_info.protected_content completely matches with an existing
|
||||
// AdaptationSet, then it will return the pointer.
|
||||
AdaptationSet* GetAdaptationSetForMediaInfo(const MediaInfo& media_info,
|
||||
ContentType type,
|
||||
const std::string& language);
|
||||
|
||||
// Sets a group id for |adaptation_set| if applicable.
|
||||
// If a group ID is already assigned, then this returns immediately.
|
||||
// |type| and |language| are the type and language of |adaptation_set|.
|
||||
void SetGroupId(ContentType type,
|
||||
const std::string& language,
|
||||
AdaptationSet* adaptation_set);
|
||||
|
||||
// Helper function to get a new AdaptationSet; registers the values
|
||||
// to the fields (maps) of the instance.
|
||||
// If the media is encrypted, registers data to protected_content_map_.
|
||||
AdaptationSet* NewAdaptationSet(const MediaInfo& media_info,
|
||||
const std::string& language,
|
||||
std::list<AdaptationSet*>* adaptation_sets);
|
||||
|
||||
// Testing only method. Returns a pointer to MpdBuilder.
|
||||
MpdBuilder* MpdBuilderForTesting() const {
|
||||
return mpd_builder_.get();
|
||||
}
|
||||
|
||||
// Testing only method. Sets mpd_builder_.
|
||||
void SetMpdBuilderForTesting(scoped_ptr<MpdBuilder> mpd_builder) {
|
||||
mpd_builder_ = mpd_builder.Pass();
|
||||
}
|
||||
|
||||
// [type][lang] = list<AdaptationSet>
|
||||
// Note: lang can be empty, e.g. for video.
|
||||
std::map<ContentType, std::map<std::string, std::list<AdaptationSet*> > >
|
||||
adaptation_set_list_map_;
|
||||
RepresentationMap representation_map_;
|
||||
|
||||
// Used to check whether a Representation should be added to an AdaptationSet.
|
||||
ProtectedContentMap protected_content_map_;
|
||||
|
||||
// MPD output path.
|
||||
std::string output_path_;
|
||||
scoped_ptr<MpdBuilder> mpd_builder_;
|
||||
base::Lock lock_;
|
||||
|
||||
// Next group ID to use for AdapationSets that can be grouped.
|
||||
int next_group_id_;
|
||||
};
|
||||
|
||||
} // namespace edash_packager
|
||||
|
||||
#endif // MPD_BASE_DASH_IOP_MPD_NOTIFIER_H_
|
|
@ -0,0 +1,692 @@
|
|||
// 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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "packager/base/file_util.h"
|
||||
#include "packager/base/files/file_path.h"
|
||||
#include "packager/mpd/base/dash_iop_mpd_notifier.h"
|
||||
#include "packager/mpd/base/mock_mpd_builder.h"
|
||||
#include "packager/mpd/base/mpd_builder.h"
|
||||
#include "packager/mpd/test/mpd_builder_test_helper.h"
|
||||
|
||||
namespace edash_packager {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Eq;
|
||||
using ::testing::InSequence;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kValidMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 1280\n"
|
||||
" height: 720\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
const uint32_t kDefaultAdaptationSetId = 0u;
|
||||
const uint32_t kDefaultRepresentationId = 1u;
|
||||
const int kDefaultGroupId = -1;
|
||||
|
||||
bool ElementEqual(const edash_packager::Element& lhs,
|
||||
const edash_packager::Element& rhs) {
|
||||
const bool all_equal_except_sublement_check =
|
||||
lhs.name == rhs.name && lhs.attributes.size() == rhs.attributes.size() &&
|
||||
std::equal(lhs.attributes.begin(), lhs.attributes.end(),
|
||||
rhs.attributes.begin()) &&
|
||||
lhs.content == rhs.content &&
|
||||
lhs.subelements.size() == rhs.subelements.size();
|
||||
|
||||
if (!all_equal_except_sublement_check) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < lhs.subelements.size(); ++i) {
|
||||
if (!ElementEqual(lhs.subelements[i], rhs.subelements[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ContentProtectionElementEqual(
|
||||
const edash_packager::ContentProtectionElement& lhs,
|
||||
const edash_packager::ContentProtectionElement& rhs) {
|
||||
const bool all_equal_except_sublement_check =
|
||||
lhs.value == rhs.value && lhs.scheme_id_uri == rhs.scheme_id_uri &&
|
||||
lhs.additional_attributes.size() == rhs.additional_attributes.size() &&
|
||||
std::equal(lhs.additional_attributes.begin(),
|
||||
lhs.additional_attributes.end(),
|
||||
rhs.additional_attributes.begin()) &&
|
||||
lhs.subelements.size() == rhs.subelements.size();
|
||||
|
||||
if (!all_equal_except_sublement_check)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < lhs.subelements.size(); ++i) {
|
||||
if (!ElementEqual(lhs.subelements[i], rhs.subelements[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MATCHER_P(ContentProtectionElementEq, expected, "") {
|
||||
return ContentProtectionElementEqual(arg, expected);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(rkuroiwa): This is almost exactly the same as SimpleMpdNotifierTest but
|
||||
// replaced all SimpleMpd with DashIopMpd,
|
||||
// use typed tests
|
||||
// (https://code.google.com/p/googletest/wiki/AdvancedGuide#Typed_Tests);
|
||||
// also because SimpleMpdNotifier and DashIopMpdNotifier have common behavior
|
||||
// for most of the public functions.
|
||||
class DashIopMpdNotifierTest
|
||||
: public ::testing::TestWithParam<MpdBuilder::MpdType> {
|
||||
protected:
|
||||
DashIopMpdNotifierTest()
|
||||
: default_mock_adaptation_set_(
|
||||
new MockAdaptationSet(kDefaultAdaptationSetId)),
|
||||
default_mock_representation_(
|
||||
new MockRepresentation(kDefaultRepresentationId)) {}
|
||||
|
||||
virtual void SetUp() OVERRIDE {
|
||||
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
|
||||
output_path_ = temp_file_path_.value();
|
||||
ON_CALL(*default_mock_adaptation_set_, Group())
|
||||
.WillByDefault(Return(kDefaultGroupId));
|
||||
}
|
||||
|
||||
virtual void TearDown() OVERRIDE {
|
||||
base::DeleteFile(temp_file_path_, false /* non recursive, just 1 file */);
|
||||
}
|
||||
|
||||
MpdBuilder::MpdType GetMpdBuilderType(const DashIopMpdNotifier& notifier) {
|
||||
return notifier.MpdBuilderForTesting()->type();
|
||||
}
|
||||
|
||||
void SetMpdBuilder(DashIopMpdNotifier* notifier,
|
||||
scoped_ptr<MpdBuilder> mpd_builder) {
|
||||
notifier->SetMpdBuilderForTesting(mpd_builder.Pass());
|
||||
}
|
||||
|
||||
MpdBuilder::MpdType mpd_type() {
|
||||
return GetParam();
|
||||
}
|
||||
|
||||
DashProfile dash_profile() {
|
||||
return mpd_type() == MpdBuilder::kStatic ? kOnDemandProfile : kLiveProfile;
|
||||
}
|
||||
|
||||
// Use output_path_ for specifying the MPD output path so that
|
||||
// WriteMpdToFile() doesn't crash.
|
||||
std::string output_path_;
|
||||
const MpdOptions empty_mpd_option_;
|
||||
const std::vector<std::string> empty_base_urls_;
|
||||
|
||||
// Default AdaptationSet mock.
|
||||
scoped_ptr<MockAdaptationSet> default_mock_adaptation_set_;
|
||||
scoped_ptr<MockRepresentation> default_mock_representation_;
|
||||
|
||||
private:
|
||||
base::FilePath temp_file_path_;
|
||||
};
|
||||
|
||||
// Verify that it creates the correct MpdBuilder type using DashProfile passed
|
||||
// to the constructor.
|
||||
TEST_F(DashIopMpdNotifierTest, CreateCorrectMpdBuilderType) {
|
||||
DashIopMpdNotifier on_demand_notifier(kOnDemandProfile, empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
EXPECT_TRUE(on_demand_notifier.Init());
|
||||
EXPECT_EQ(MpdBuilder::kStatic, GetMpdBuilderType(on_demand_notifier));
|
||||
DashIopMpdNotifier live_notifier(kLiveProfile, empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
EXPECT_TRUE(live_notifier.Init());
|
||||
EXPECT_EQ(MpdBuilder::kDynamic, GetMpdBuilderType(live_notifier));
|
||||
}
|
||||
|
||||
// Verify that basic VOD NotifyNewContainer() operation works.
|
||||
// No encrypted contents.
|
||||
TEST_P(DashIopMpdNotifierTest, NotifyNewContainer) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
|
||||
scoped_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder(mpd_type()));
|
||||
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||
.WillOnce(Return(default_mock_representation_.get()));
|
||||
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
uint32_t unused_container_id;
|
||||
SetMpdBuilder(¬ifier, mock_mpd_builder.PassAs<MpdBuilder>());
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
||||
&unused_container_id));
|
||||
}
|
||||
|
||||
// Verify VOD NotifyNewContainer() operation works with different
|
||||
// MediaInfo::ProtectedContent.
|
||||
// Two AdaptationSets should be created.
|
||||
// Different DRM so they won't be grouped.
|
||||
TEST_P(DashIopMpdNotifierTest,
|
||||
NotifyNewContainersWithDifferentProtectedContent) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
scoped_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder(mpd_type()));
|
||||
|
||||
// Note they both have different (bogus) pssh, like real use case.
|
||||
// default Key ID = _default_key_id_
|
||||
const char kSdProtectedContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 640\n"
|
||||
" height: 360\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'myuuid'\n"
|
||||
" name_version: 'MyContentProtection version 1'\n"
|
||||
" pssh: 'pssh1'\n"
|
||||
" }\n"
|
||||
" default_key_id: '_default_key_id_'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
// default Key ID = .default.key.id.
|
||||
const char kHdProtectedContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 1280\n"
|
||||
" height: 720\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'anotheruuid'\n"
|
||||
" name_version: 'SomeOtherProtection version 3'\n"
|
||||
" pssh: 'pssh2'\n"
|
||||
" }\n"
|
||||
" default_key_id: '.default.key.id.'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
// Check that the right ContentProtectionElements for SD is created.
|
||||
// HD is the same case, so not checking.
|
||||
ContentProtectionElement mp4_protection;
|
||||
mp4_protection.scheme_id_uri = "urn:mpeg:dash:mp4protection:2011";
|
||||
mp4_protection.value = "cenc";
|
||||
// This should match the "_default_key_id_" above, but taking it as hex data
|
||||
// and converted to UUID format.
|
||||
mp4_protection.additional_attributes["cenc:default_KID"] =
|
||||
"5f646566-6175-6c74-5f6b-65795f69645f";
|
||||
ContentProtectionElement sd_my_drm;
|
||||
sd_my_drm.scheme_id_uri = "urn:uuid:myuuid";
|
||||
sd_my_drm.value = "MyContentProtection version 1";
|
||||
Element cenc_pssh;
|
||||
cenc_pssh.name = "cenc:pssh";
|
||||
cenc_pssh.content = "cHNzaDE="; // Base64 encoding of 'pssh1'.
|
||||
sd_my_drm.subelements.push_back(cenc_pssh);
|
||||
|
||||
// Not using default mocks in this test so that we can keep track of
|
||||
// mocks by named mocks.
|
||||
const uint32_t kSdAdaptationSetId = 2u;
|
||||
const uint32_t kHdAdaptationSetId = 3u;
|
||||
scoped_ptr<MockAdaptationSet> sd_adaptation_set(
|
||||
new MockAdaptationSet(kSdAdaptationSetId));
|
||||
scoped_ptr<MockAdaptationSet> hd_adaptation_set(
|
||||
new MockAdaptationSet(kHdAdaptationSetId));
|
||||
|
||||
ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId));
|
||||
ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId));
|
||||
EXPECT_CALL(*sd_adaptation_set, SetGroup(_)).Times(0);
|
||||
EXPECT_CALL(*hd_adaptation_set, SetGroup(_)).Times(0);
|
||||
|
||||
const uint32_t kSdRepresentation = 4u;
|
||||
const uint32_t kHdRepresentation = 5u;
|
||||
scoped_ptr<MockRepresentation> sd_representation(
|
||||
new MockRepresentation(kSdRepresentation));
|
||||
scoped_ptr<MockRepresentation> hd_representation(
|
||||
new MockRepresentation(kHdRepresentation));
|
||||
|
||||
InSequence in_sequence;
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(sd_adaptation_set.get()));
|
||||
EXPECT_CALL(
|
||||
*sd_adaptation_set,
|
||||
AddContentProtectionElement(ContentProtectionElementEq(mp4_protection)));
|
||||
EXPECT_CALL(*sd_adaptation_set, AddContentProtectionElement(
|
||||
ContentProtectionElementEq(sd_my_drm)));
|
||||
EXPECT_CALL(*sd_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(sd_representation.get()));
|
||||
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(hd_adaptation_set.get()));
|
||||
// Called twice for the same reason as above.
|
||||
EXPECT_CALL(*hd_adaptation_set, AddContentProtectionElement(_)).Times(2);
|
||||
EXPECT_CALL(*hd_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(hd_representation.get()));
|
||||
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
uint32_t unused_container_id;
|
||||
SetMpdBuilder(¬ifier, mock_mpd_builder.PassAs<MpdBuilder>());
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
|
||||
}
|
||||
|
||||
// Verify VOD NotifyNewContainer() operation works with same
|
||||
// MediaInfo::ProtectedContent. Only one AdaptationSet should be
|
||||
// created.
|
||||
TEST_P(DashIopMpdNotifierTest, NotifyNewContainersWithSameProtectedContent) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
scoped_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder(mpd_type()));
|
||||
|
||||
// These have the same default key ID and PSSH.
|
||||
const char kSdProtectedContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 640\n"
|
||||
" height: 360\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'myuuid'\n"
|
||||
" name_version: 'MyContentProtection version 1'\n"
|
||||
" pssh: 'psshbox'\n"
|
||||
" }\n"
|
||||
" default_key_id: '.DEFAULT.KEY.ID.'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
const char kHdProtectedContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 1280\n"
|
||||
" height: 720\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'myuuid'\n"
|
||||
" name_version: 'MyContentProtection version 1'\n"
|
||||
" pssh: 'psshbox'\n"
|
||||
" }\n"
|
||||
" default_key_id: '.DEFAULT.KEY.ID.'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
ContentProtectionElement mp4_protection;
|
||||
mp4_protection.scheme_id_uri = "urn:mpeg:dash:mp4protection:2011";
|
||||
mp4_protection.value = "cenc";
|
||||
// This should match the ".DEFAULT.KEY.ID." above, but taking it as hex data
|
||||
// and converted to UUID format.
|
||||
mp4_protection.additional_attributes["cenc:default_KID"] =
|
||||
"2e444546-4155-4c54-2e4b-45592e49442e";
|
||||
ContentProtectionElement my_drm;
|
||||
my_drm.scheme_id_uri = "urn:uuid:myuuid";
|
||||
my_drm.value = "MyContentProtection version 1";
|
||||
Element cenc_pssh;
|
||||
cenc_pssh.name = "cenc:pssh";
|
||||
cenc_pssh.content = "cHNzaGJveA=="; // Base64 encoding of 'psshbox'.
|
||||
my_drm.subelements.push_back(cenc_pssh);
|
||||
|
||||
const uint32_t kSdRepresentation = 6u;
|
||||
const uint32_t kHdRepresentation = 7u;
|
||||
scoped_ptr<MockRepresentation> sd_representation(
|
||||
new MockRepresentation(kSdRepresentation));
|
||||
scoped_ptr<MockRepresentation> hd_representation(
|
||||
new MockRepresentation(kHdRepresentation));
|
||||
|
||||
// No reason to set @group if there is only one AdaptationSet.
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, SetGroup(_)).Times(0);
|
||||
|
||||
InSequence in_sequence;
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||
EXPECT_CALL(
|
||||
*default_mock_adaptation_set_,
|
||||
AddContentProtectionElement(ContentProtectionElementEq(mp4_protection)));
|
||||
EXPECT_CALL(*default_mock_adaptation_set_,
|
||||
AddContentProtectionElement(ContentProtectionElementEq(my_drm)));
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||
.WillOnce(Return(sd_representation.get()));
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
// For second representation, no new AddAdaptationSet().
|
||||
// And make sure that AddContentProtection() is not called.
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_)).Times(0);
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, AddContentProtectionElement(_))
|
||||
.Times(0);
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||
.WillOnce(Return(hd_representation.get()));
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
uint32_t unused_container_id;
|
||||
SetMpdBuilder(¬ifier, mock_mpd_builder.PassAs<MpdBuilder>());
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
|
||||
}
|
||||
|
||||
// AddContentProtection() should not work and should always return false.
|
||||
TEST_P(DashIopMpdNotifierTest, AddContentProtection) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
|
||||
scoped_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder(mpd_type()));
|
||||
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||
.WillOnce(Return(default_mock_representation_.get()));
|
||||
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
uint32_t container_id;
|
||||
SetMpdBuilder(¬ifier, mock_mpd_builder.PassAs<MpdBuilder>());
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
||||
&container_id));
|
||||
|
||||
ContentProtectionElement empty_content_protection_element;
|
||||
EXPECT_FALSE(notifier.AddContentProtectionElement(
|
||||
container_id, empty_content_protection_element));
|
||||
}
|
||||
|
||||
// Default Key IDs are different but if the content protection UUIDs match, then
|
||||
// they can be in the same group.
|
||||
// This is a long test.
|
||||
// Basically this
|
||||
// 1. Add an SD protected content. This should make an AdaptationSet.
|
||||
// 2. Add an HD protected content. This should make another AdaptationSet that
|
||||
// is different from the SD version. Both SD and HD should have the same
|
||||
// group ID assigned.
|
||||
// 3. Add a 4k protected content. This should also make a new AdaptationSet.
|
||||
// The group ID should also match the SD and HD (but this takes a slightly
|
||||
// different path).
|
||||
TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
scoped_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder(mpd_type()));
|
||||
|
||||
// These have the same default key ID and PSSH.
|
||||
const char kSdProtectedContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 640\n"
|
||||
" height: 360\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'myuuid'\n"
|
||||
" name_version: 'MyContentProtection version 1'\n"
|
||||
" pssh: 'pssh_sd'\n"
|
||||
" }\n"
|
||||
" default_key_id: '_default_key_id_'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
const char kHdProtectedContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 1280\n"
|
||||
" height: 720\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'myuuid'\n"
|
||||
" name_version: 'MyContentProtection version 1'\n"
|
||||
" pssh: 'pssh_hd'\n"
|
||||
" }\n"
|
||||
" default_key_id: '.DEFAULT.KEY.ID.'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
const uint32_t kSdAdaptationSetId = 6u;
|
||||
const uint32_t kHdAdaptationSetId = 7u;
|
||||
scoped_ptr<MockAdaptationSet> sd_adaptation_set(
|
||||
new MockAdaptationSet(kSdAdaptationSetId));
|
||||
scoped_ptr<MockAdaptationSet> hd_adaptation_set(
|
||||
new MockAdaptationSet(kHdAdaptationSetId));
|
||||
|
||||
ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId));
|
||||
ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId));
|
||||
|
||||
const uint32_t kSdRepresentation = 4u;
|
||||
const uint32_t kHdRepresentation = 5u;
|
||||
scoped_ptr<MockRepresentation> sd_representation(
|
||||
new MockRepresentation(kSdRepresentation));
|
||||
scoped_ptr<MockRepresentation> hd_representation(
|
||||
new MockRepresentation(kHdRepresentation));
|
||||
|
||||
InSequence in_sequence;
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(sd_adaptation_set.get()));
|
||||
EXPECT_CALL(*sd_adaptation_set, AddContentProtectionElement(_)).Times(2);
|
||||
EXPECT_CALL(*sd_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(sd_representation.get()));
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(hd_adaptation_set.get()));
|
||||
EXPECT_CALL(*hd_adaptation_set, AddContentProtectionElement(_)).Times(2);
|
||||
EXPECT_CALL(*hd_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(hd_representation.get()));
|
||||
|
||||
// Both AdaptationSets' groups should be set to the same value.
|
||||
const int kExpectedGroupId = 1;
|
||||
EXPECT_CALL(*sd_adaptation_set, SetGroup(kExpectedGroupId));
|
||||
EXPECT_CALL(*hd_adaptation_set, SetGroup(kExpectedGroupId));
|
||||
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
// This is not very nice but we need it for settings expectations later.
|
||||
MockMpdBuilder* mock_mpd_builder_raw = mock_mpd_builder.get();
|
||||
uint32_t unused_container_id;
|
||||
SetMpdBuilder(¬ifier, mock_mpd_builder.PassAs<MpdBuilder>());
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
|
||||
|
||||
// Now that the group IDs are set Group() returns kExpectedGroupId.
|
||||
ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kExpectedGroupId));
|
||||
ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kExpectedGroupId));
|
||||
|
||||
// Add another content that has the same protected content and make sure that
|
||||
// it gets added to the existing group.
|
||||
const char k4kProtectedContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 4096\n"
|
||||
" height: 2160\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'myuuid'\n"
|
||||
" name_version: 'MyContentProtection version 1'\n"
|
||||
" pssh: 'pssh_4k'\n"
|
||||
" }\n"
|
||||
" default_key_id: 'some16bytestring'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
const uint32_t k4kAdaptationSetId = 4000u;
|
||||
scoped_ptr<MockAdaptationSet> fourk_adaptation_set(
|
||||
new MockAdaptationSet(k4kAdaptationSetId));
|
||||
ON_CALL(*fourk_adaptation_set, Group())
|
||||
.WillByDefault(Return(kDefaultGroupId));
|
||||
|
||||
const uint32_t k4kRepresentationId = 4001u;
|
||||
scoped_ptr<MockRepresentation> fourk_representation(
|
||||
new MockRepresentation(k4kRepresentationId));
|
||||
|
||||
EXPECT_CALL(*mock_mpd_builder_raw, AddAdaptationSet(_))
|
||||
.WillOnce(Return(fourk_adaptation_set.get()));
|
||||
EXPECT_CALL(*fourk_adaptation_set, AddContentProtectionElement(_)).Times(2);
|
||||
EXPECT_CALL(*fourk_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(fourk_representation.get()));
|
||||
|
||||
// Same group ID should be set.
|
||||
EXPECT_CALL(*fourk_adaptation_set, SetGroup(kExpectedGroupId));
|
||||
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder_raw, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(k4kProtectedContent), &unused_container_id));
|
||||
}
|
||||
|
||||
// Even if the UUIDs match, video and audio AdaptationSets should not be grouped
|
||||
// together.
|
||||
TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
scoped_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder(mpd_type()));
|
||||
|
||||
// These have the same default key ID and PSSH.
|
||||
const char kVideoContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 640\n"
|
||||
" height: 360\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'myuuid'\n"
|
||||
" name_version: 'MyContentProtection version 1'\n"
|
||||
" pssh: 'pssh_video'\n"
|
||||
" }\n"
|
||||
" default_key_id: '_default_key_id_'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
const char kAudioContent[] =
|
||||
"audio_info {\n"
|
||||
" codec: 'mp4a.40.2'\n"
|
||||
" sampling_frequency: 44100\n"
|
||||
" time_scale: 1200\n"
|
||||
" num_channels: 2\n"
|
||||
"}\n"
|
||||
"protected_content {\n"
|
||||
" content_protection_entry {\n"
|
||||
" uuid: 'myuuid'\n"
|
||||
" name_version: 'MyContentProtection version 1'\n"
|
||||
" pssh: 'pssh_audio'\n"
|
||||
" }\n"
|
||||
" default_key_id: '_default_key_id_'\n"
|
||||
"}\n"
|
||||
"reference_time_scale: 50\n"
|
||||
"container_type: 1\n"
|
||||
"media_duration_seconds: 10.5\n";
|
||||
|
||||
const uint32_t kSdAdaptationSetId = 6u;
|
||||
const uint32_t kHdAdaptationSetId = 7u;
|
||||
scoped_ptr<MockAdaptationSet> video_adaptation_set(
|
||||
new MockAdaptationSet(kSdAdaptationSetId));
|
||||
scoped_ptr<MockAdaptationSet> audio_adaptation_set(
|
||||
new MockAdaptationSet(kHdAdaptationSetId));
|
||||
|
||||
ON_CALL(*video_adaptation_set, Group())
|
||||
.WillByDefault(Return(kDefaultGroupId));
|
||||
ON_CALL(*audio_adaptation_set, Group())
|
||||
.WillByDefault(Return(kDefaultGroupId));
|
||||
|
||||
// Both AdaptationSets' groups should NOT be set.
|
||||
EXPECT_CALL(*video_adaptation_set, SetGroup(_)).Times(0);
|
||||
EXPECT_CALL(*audio_adaptation_set, SetGroup(_)).Times(0);
|
||||
|
||||
const uint32_t kSdRepresentation = 8u;
|
||||
const uint32_t kHdRepresentation = 9u;
|
||||
scoped_ptr<MockRepresentation> sd_representation(
|
||||
new MockRepresentation(kSdRepresentation));
|
||||
scoped_ptr<MockRepresentation> hd_representation(
|
||||
new MockRepresentation(kHdRepresentation));
|
||||
|
||||
InSequence in_sequence;
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(video_adaptation_set.get()));
|
||||
EXPECT_CALL(*video_adaptation_set, AddContentProtectionElement(_)).Times(2);
|
||||
EXPECT_CALL(*video_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(sd_representation.get()));
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(audio_adaptation_set.get()));
|
||||
EXPECT_CALL(*audio_adaptation_set, AddContentProtectionElement(_)).Times(2);
|
||||
EXPECT_CALL(*audio_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(hd_representation.get()));
|
||||
|
||||
if (mpd_type() == MpdBuilder::kStatic)
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
uint32_t unused_container_id;
|
||||
SetMpdBuilder(¬ifier, mock_mpd_builder.PassAs<MpdBuilder>());
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kVideoContent), &unused_container_id));
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kAudioContent), &unused_container_id));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(StaticAndDynamic,
|
||||
DashIopMpdNotifierTest,
|
||||
::testing::Values(MpdBuilder::kStatic,
|
||||
MpdBuilder::kDynamic));
|
||||
|
||||
} // namespace edash_packager
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
namespace edash_packager {
|
||||
namespace {
|
||||
const uint32_t kAnyAdaptationSetId = 1;
|
||||
const char kEmptyLang[] = "";
|
||||
const MpdOptions kDefaultMpdOptions;
|
||||
const MpdBuilder::MpdType kDefaultMpdType = MpdBuilder::kStatic;
|
||||
|
@ -16,8 +15,8 @@ MockMpdBuilder::MockMpdBuilder(MpdType type)
|
|||
: MpdBuilder(type, kDefaultMpdOptions) {}
|
||||
MockMpdBuilder::~MockMpdBuilder() {}
|
||||
|
||||
MockAdaptationSet::MockAdaptationSet()
|
||||
: AdaptationSet(kAnyAdaptationSetId,
|
||||
MockAdaptationSet::MockAdaptationSet(uint32_t adaptation_set_id)
|
||||
: AdaptationSet(adaptation_set_id,
|
||||
kEmptyLang,
|
||||
kDefaultMpdOptions,
|
||||
kDefaultMpdType,
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace edash_packager {
|
|||
|
||||
class MockMpdBuilder : public MpdBuilder {
|
||||
public:
|
||||
// type indicates whether the MPD should be for VOD or live content (kStatic
|
||||
// |type| indicates whether the MPD should be for VOD or live content (kStatic
|
||||
// for VOD profile, or kDynamic for live profile).
|
||||
explicit MockMpdBuilder(MpdType type);
|
||||
virtual ~MockMpdBuilder() OVERRIDE;
|
||||
|
@ -28,10 +28,16 @@ class MockMpdBuilder : public MpdBuilder {
|
|||
|
||||
class MockAdaptationSet : public AdaptationSet {
|
||||
public:
|
||||
MockAdaptationSet();
|
||||
// |adaptation_set_id| is the id for the AdaptationSet.
|
||||
explicit MockAdaptationSet(uint32_t adaptation_set_id);
|
||||
virtual ~MockAdaptationSet() OVERRIDE;
|
||||
|
||||
MOCK_METHOD1(AddRepresentation, Representation*(const MediaInfo& media_info));
|
||||
MOCK_METHOD1(AddContentProtectionElement,
|
||||
void(const ContentProtectionElement& element));
|
||||
|
||||
MOCK_METHOD1(SetGroup, void(int group_number));
|
||||
MOCK_CONST_METHOD0(Group, int());
|
||||
|
||||
private:
|
||||
// Only for constructing the super class. Not used for testing.
|
||||
|
|
|
@ -757,6 +757,14 @@ void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) {
|
|||
force_set_segment_alignment_ = true;
|
||||
}
|
||||
|
||||
void AdaptationSet::SetGroup(int group_number) {
|
||||
group_ = group_number;
|
||||
}
|
||||
|
||||
int AdaptationSet::Group() const {
|
||||
return group_;
|
||||
}
|
||||
|
||||
void AdaptationSet::OnNewSegmentForRepresentation(uint32_t representation_id,
|
||||
uint64_t start_time,
|
||||
uint64_t duration) {
|
||||
|
|
|
@ -187,7 +187,8 @@ class AdaptationSet {
|
|||
/// If @a element has {value, schemeIdUri} set and has
|
||||
/// {“value”, “schemeIdUri”} as key for @a additional_attributes,
|
||||
/// then the former is used.
|
||||
void AddContentProtectionElement(const ContentProtectionElement& element);
|
||||
virtual void AddContentProtectionElement(
|
||||
const ContentProtectionElement& element);
|
||||
|
||||
/// Set the Role element for this AdaptationSet.
|
||||
/// The Role element's is schemeIdUri='urn:mpeg:dash:role:2011'.
|
||||
|
@ -213,9 +214,10 @@ class AdaptationSet {
|
|||
/// Note that group=0 is a special group, as mentioned in the DASH MPD
|
||||
/// specification.
|
||||
/// @param group_number is the value of AdaptatoinSet@group.
|
||||
void set_group(int group_number) {
|
||||
group_ = group_number;
|
||||
}
|
||||
virtual void SetGroup(int group_number);
|
||||
|
||||
/// @return Returns the value for group. If not set, returns a negative value.
|
||||
virtual int Group() const;
|
||||
|
||||
// Must be unique in the Period.
|
||||
uint32_t id() const { return id_; }
|
||||
|
|
|
@ -351,14 +351,14 @@ TEST_F(CommonMpdBuilderTest, SetAdaptationSetGroup) {
|
|||
base::AtomicSequenceNumber sequence_counter;
|
||||
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set.set_group(1);
|
||||
adaptation_set.SetGroup(1);
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type xml_with_group(adaptation_set.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeEqString("group", "1", xml_with_group.get()));
|
||||
|
||||
// Unset by passing a negative value.
|
||||
adaptation_set.set_group(-1);
|
||||
adaptation_set.SetGroup(-1);
|
||||
xml::ScopedXmlPtr<xmlNode>::type xml_without_group(adaptation_set.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeNotSet("group", xml_without_group.get()));
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "packager/base/macros.h"
|
||||
|
||||
namespace edash_packager {
|
||||
|
||||
class MediaInfo;
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
// 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/mpd_notifier_util.h"
|
||||
|
||||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/base/strings/string_util.h"
|
||||
#include "packager/media/file/file_closer.h"
|
||||
#include "packager/media/file/file.h"
|
||||
#include "packager/mpd/base/mpd_utils.h"
|
||||
|
||||
namespace edash_packager {
|
||||
|
||||
using media::File;
|
||||
using media::FileCloser;
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper function for adding ContentProtection for AdaptatoinSet or
|
||||
// Representation.
|
||||
// Works because both classes have AddContentProtectionElement().
|
||||
template <typename ContentProtectionParent>
|
||||
void AddContentProtectionElementsHelper(const MediaInfo& media_info,
|
||||
ContentProtectionParent* parent) {
|
||||
DCHECK(parent);
|
||||
if (!media_info.has_protected_content())
|
||||
return;
|
||||
|
||||
const MediaInfo::ProtectedContent& protected_content =
|
||||
media_info.protected_content();
|
||||
|
||||
const char kEncryptedMp4Uri[] = "urn:mpeg:dash:mp4protection:2011";
|
||||
const char kEncryptedMp4Value[] = "cenc";
|
||||
|
||||
// DASH MPD spec specifies a default ContentProtection element for ISO BMFF
|
||||
// (MP4) files.
|
||||
const bool is_mp4_container =
|
||||
media_info.container_type() == MediaInfo::CONTAINER_MP4;
|
||||
if (is_mp4_container) {
|
||||
ContentProtectionElement mp4_content_protection;
|
||||
mp4_content_protection.scheme_id_uri = kEncryptedMp4Uri;
|
||||
mp4_content_protection.value = kEncryptedMp4Value;
|
||||
if (protected_content.has_default_key_id()) {
|
||||
std::string key_id_uuid_format;
|
||||
if (HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) {
|
||||
mp4_content_protection.additional_attributes["cenc:default_KID"] =
|
||||
key_id_uuid_format;
|
||||
} else {
|
||||
LOG(ERROR) << "Failed to convert default key ID into UUID format.";
|
||||
}
|
||||
}
|
||||
|
||||
parent->AddContentProtectionElement(mp4_content_protection);
|
||||
}
|
||||
|
||||
for (int i = 0; i < protected_content.content_protection_entry().size();
|
||||
++i) {
|
||||
const MediaInfo::ProtectedContent::ContentProtectionEntry& entry =
|
||||
protected_content.content_protection_entry(i);
|
||||
if (!entry.has_uuid()) {
|
||||
LOG(WARNING)
|
||||
<< "ContentProtectionEntry was specified but no UUID is set for "
|
||||
<< entry.name_version() << ", skipping.";
|
||||
continue;
|
||||
}
|
||||
|
||||
ContentProtectionElement drm_content_protection;
|
||||
drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid();
|
||||
if (entry.has_name_version())
|
||||
drm_content_protection.value = entry.name_version();
|
||||
|
||||
if (entry.has_pssh()) {
|
||||
std::string base64_encoded_pssh;
|
||||
base::Base64Encode(entry.pssh(), &base64_encoded_pssh);
|
||||
Element cenc_pssh;
|
||||
cenc_pssh.name = "cenc:pssh";
|
||||
cenc_pssh.content = base64_encoded_pssh;
|
||||
drm_content_protection.subelements.push_back(cenc_pssh);
|
||||
}
|
||||
|
||||
parent->AddContentProtectionElement(drm_content_protection);
|
||||
}
|
||||
|
||||
LOG_IF(WARNING, protected_content.content_protection_entry().size() == 0)
|
||||
<< "The media is encrypted but no content protection specified.";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Coverts binary data into human readable UUID format.
|
||||
bool HexToUUID(const std::string& data, std::string* uuid_format) {
|
||||
DCHECK(uuid_format);
|
||||
const size_t kExpectedUUIDSize = 16;
|
||||
if (data.size() != kExpectedUUIDSize) {
|
||||
LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize
|
||||
<< " but is " << data.size() << " and the data in hex is "
|
||||
<< base::HexEncode(data.data(), data.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string hex_encoded =
|
||||
StringToLowerASCII(base::HexEncode(data.data(), data.size()));
|
||||
DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2);
|
||||
base::StringPiece all(hex_encoded);
|
||||
// Note UUID has 5 parts separated with dashes.
|
||||
// e.g. 123e4567-e89b-12d3-a456-426655440000
|
||||
// These StringPieces have each part.
|
||||
base::StringPiece first = all.substr(0, 8);
|
||||
base::StringPiece second = all.substr(8, 4);
|
||||
base::StringPiece third = all.substr(12, 4);
|
||||
base::StringPiece fourth = all.substr(16, 4);
|
||||
base::StringPiece fifth = all.substr(20, 12);
|
||||
|
||||
// 32 hexadecimal characters with 4 hyphens.
|
||||
const size_t kHumanReadableUUIDSize = 36;
|
||||
uuid_format->reserve(kHumanReadableUUIDSize);
|
||||
first.CopyToString(uuid_format);
|
||||
uuid_format->append("-");
|
||||
second.AppendToString(uuid_format);
|
||||
uuid_format->append("-");
|
||||
third.AppendToString(uuid_format);
|
||||
uuid_format->append("-");
|
||||
fourth.AppendToString(uuid_format);
|
||||
uuid_format->append("-");
|
||||
fifth.AppendToString(uuid_format);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteMpdToFile(const std::string& output_path, MpdBuilder* mpd_builder) {
|
||||
CHECK(!output_path.empty());
|
||||
|
||||
std::string mpd;
|
||||
if (!mpd_builder->ToString(&mpd)) {
|
||||
LOG(ERROR) << "Failed to write MPD to string.";
|
||||
return false;
|
||||
}
|
||||
|
||||
scoped_ptr<File, FileCloser> file(File::Open(output_path.c_str(), "w"));
|
||||
if (!file) {
|
||||
LOG(ERROR) << "Failed to open file for writing: " << output_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* mpd_char_ptr = mpd.data();
|
||||
size_t mpd_bytes_left = mpd.size();
|
||||
while (mpd_bytes_left > 0) {
|
||||
int64_t length = file->Write(mpd_char_ptr, mpd_bytes_left);
|
||||
if (length <= 0) {
|
||||
LOG(ERROR) << "Failed to write to file '" << output_path << "' ("
|
||||
<< length << ").";
|
||||
return false;
|
||||
}
|
||||
mpd_char_ptr += length;
|
||||
mpd_bytes_left -= length;
|
||||
}
|
||||
// Release the pointer because Close() destructs itself.
|
||||
return file.release()->Close();
|
||||
}
|
||||
|
||||
ContentType GetContentType(const MediaInfo& media_info) {
|
||||
const bool has_video = media_info.has_video_info();
|
||||
const bool has_audio = media_info.has_audio_info();
|
||||
const bool has_text = media_info.has_text_info();
|
||||
|
||||
if (MoreThanOneTrue(has_video, has_audio, has_text)) {
|
||||
NOTIMPLEMENTED() << "MediaInfo with more than one stream is not supported.";
|
||||
return kContentTypeUnknown;
|
||||
}
|
||||
if (!AtLeastOneTrue(has_video, has_audio, has_text)) {
|
||||
LOG(ERROR) << "MediaInfo should contain one audio, video, or text stream.";
|
||||
return kContentTypeUnknown;
|
||||
}
|
||||
return has_video ? kContentTypeVideo
|
||||
: (has_audio ? kContentTypeAudio : kContentTypeText);
|
||||
}
|
||||
|
||||
void AddContentProtectionElements(const MediaInfo& media_info,
|
||||
AdaptationSet* adaptation_set) {
|
||||
AddContentProtectionElementsHelper(media_info, adaptation_set);
|
||||
}
|
||||
|
||||
void AddContentProtectionElements(const MediaInfo& media_info,
|
||||
Representation* representation) {
|
||||
AddContentProtectionElementsHelper(media_info, representation);
|
||||
}
|
||||
|
||||
} // namespace edash_packager
|
|
@ -0,0 +1,63 @@
|
|||
// 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
|
||||
|
||||
/// This file contains helper functions and enums for MpdNotifier
|
||||
/// implementations.
|
||||
|
||||
#ifndef MPD_BASE_MPD_NOTIFIER_UTIL_H_
|
||||
#define MPD_BASE_MPD_NOTIFIER_UTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "packager/base/base64.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/mpd_builder.h"
|
||||
|
||||
namespace edash_packager{
|
||||
|
||||
enum ContentType {
|
||||
kContentTypeUnknown,
|
||||
kContentTypeVideo,
|
||||
kContentTypeAudio,
|
||||
kContentTypeText
|
||||
};
|
||||
|
||||
/// Converts hex data to UUID format. Hex data must be size 16.
|
||||
/// @param data input hex data.
|
||||
/// @param uuid_format is the UUID format of the input.
|
||||
bool HexToUUID(const std::string& data, std::string* uuid_format);
|
||||
|
||||
/// Outputs MPD to @a output_path.
|
||||
/// @param output_path is the path to the MPD output location.
|
||||
/// @param mpd_builder is the MPD builder instance.
|
||||
bool WriteMpdToFile(const std::string& output_path, MpdBuilder* mpd_builder);
|
||||
|
||||
/// Determines the content type of |media_info|.
|
||||
/// @param media_info is the information about the media.
|
||||
/// @return content type of the @a media_info.
|
||||
ContentType GetContentType(const MediaInfo& media_info);
|
||||
|
||||
/// Adds <ContentProtection> elements specified by @a media_info to
|
||||
/// @a adaptation_set.
|
||||
/// Note that this will add the elements as direct chlidren of AdaptationSet.
|
||||
/// @param media_info may or may not have protected_content field.
|
||||
/// @param adaptation_set is the parent element that owns the ContentProtection
|
||||
/// elements.
|
||||
void AddContentProtectionElements(const MediaInfo& media_info,
|
||||
AdaptationSet* adaptation_set);
|
||||
|
||||
/// Adds <ContentProtection> elements specified by @a media_info to
|
||||
/// @a representation.
|
||||
/// @param media_info may or may not have protected_content field.
|
||||
/// @param representation is the parent element that owns the ContentProtection
|
||||
/// elements.
|
||||
void AddContentProtectionElements(const MediaInfo& media_info,
|
||||
Representation* representation);
|
||||
|
||||
|
||||
} // namespace edash_packager
|
||||
|
||||
#endif // MPD_BASE_MPD_NOTIFIER_UTIL_H_
|
|
@ -6,128 +6,13 @@
|
|||
|
||||
#include "packager/mpd/base/simple_mpd_notifier.h"
|
||||
|
||||
#include "packager/base/base64.h"
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/base/strings/string_util.h"
|
||||
#include "packager/media/file/file.h"
|
||||
#include "packager/mpd/base/mpd_builder.h"
|
||||
#include "packager/mpd/base/mpd_notifier_util.h"
|
||||
#include "packager/mpd/base/mpd_utils.h"
|
||||
|
||||
using edash_packager::media::File;
|
||||
|
||||
namespace edash_packager {
|
||||
|
||||
namespace {
|
||||
|
||||
// Coverts binary data into human readable UUID format.
|
||||
bool HexToUUID(const std::string& data, std::string* uuid_format) {
|
||||
DCHECK(uuid_format);
|
||||
const size_t kExpectedUUIDSize = 16;
|
||||
if (data.size() != kExpectedUUIDSize) {
|
||||
LOG(ERROR) << "Default key ID size is expected to be " << kExpectedUUIDSize
|
||||
<< " but is " << data.size();
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string hex_encoded =
|
||||
StringToLowerASCII(base::HexEncode(data.data(), data.size()));
|
||||
DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2);
|
||||
base::StringPiece all(hex_encoded);
|
||||
// Note UUID has 5 parts separated with dashes.
|
||||
// e.g. 123e4567-e89b-12d3-a456-426655440000
|
||||
// These StringPieces have each part.
|
||||
base::StringPiece first = all.substr(0, 8);
|
||||
base::StringPiece second = all.substr(8, 4);
|
||||
base::StringPiece third = all.substr(12, 4);
|
||||
base::StringPiece fourth = all.substr(16, 4);
|
||||
base::StringPiece fifth= all.substr(20, 12);
|
||||
|
||||
// 32 hexadecimal characters with 4 hyphens.
|
||||
const size_t kHumanReadableUUIDSize = 36;
|
||||
uuid_format->reserve(kHumanReadableUUIDSize);
|
||||
first.CopyToString(uuid_format);
|
||||
uuid_format->append("-");
|
||||
second.AppendToString(uuid_format);
|
||||
uuid_format->append("-");
|
||||
third.AppendToString(uuid_format);
|
||||
uuid_format->append("-");
|
||||
fourth.AppendToString(uuid_format);
|
||||
uuid_format->append("-");
|
||||
fifth.AppendToString(uuid_format);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This might be useful for DashIopCompliantMpdNotifier. If so it might make
|
||||
// sense to template this so that it accepts Representation and AdaptationSet.
|
||||
// For SimpleMpdNotifier, just put it in Representation. It should still
|
||||
// generate a valid MPD.
|
||||
void AddContentProtectionElements(const MediaInfo& media_info,
|
||||
Representation* representation) {
|
||||
DCHECK(representation);
|
||||
if (!media_info.has_protected_content())
|
||||
return;
|
||||
|
||||
const MediaInfo::ProtectedContent& protected_content =
|
||||
media_info.protected_content();
|
||||
|
||||
const char kEncryptedMp4Uri[] = "urn:mpeg:dash:mp4protection:2011";
|
||||
const char kEncryptedMp4Value[] = "cenc";
|
||||
|
||||
// DASH MPD spec specifies a default ContentProtection element for ISO BMFF
|
||||
// (MP4) files.
|
||||
const bool is_mp4_container =
|
||||
media_info.container_type() == MediaInfo::CONTAINER_MP4;
|
||||
if (is_mp4_container) {
|
||||
ContentProtectionElement mp4_content_protection;
|
||||
mp4_content_protection.scheme_id_uri = kEncryptedMp4Uri;
|
||||
mp4_content_protection.value = kEncryptedMp4Value;
|
||||
if (protected_content.has_default_key_id()) {
|
||||
std::string key_id_uuid_format;
|
||||
if (HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) {
|
||||
mp4_content_protection.additional_attributes["cenc:default_KID"] =
|
||||
key_id_uuid_format;
|
||||
} else {
|
||||
LOG(ERROR) << "Failed to convert default key ID into UUID format.";
|
||||
}
|
||||
}
|
||||
|
||||
representation->AddContentProtectionElement(mp4_content_protection);
|
||||
}
|
||||
|
||||
for (int i = 0; i < protected_content.content_protection_entry().size();
|
||||
++i) {
|
||||
const MediaInfo::ProtectedContent::ContentProtectionEntry& entry =
|
||||
protected_content.content_protection_entry(i);
|
||||
if (!entry.has_uuid()) {
|
||||
LOG(WARNING)
|
||||
<< "ContentProtectionEntry was specified but no UUID is set for "
|
||||
<< entry.name_version() << ", skipping.";
|
||||
continue;
|
||||
}
|
||||
|
||||
ContentProtectionElement drm_content_protection;
|
||||
drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid();
|
||||
if (entry.has_name_version())
|
||||
drm_content_protection.value = entry.name_version();
|
||||
|
||||
if (entry.has_pssh()) {
|
||||
std::string base64_encoded_pssh;
|
||||
base::Base64Encode(entry.pssh(), &base64_encoded_pssh);
|
||||
Element cenc_pssh;
|
||||
cenc_pssh.name = "cenc:pssh";
|
||||
cenc_pssh.content = base64_encoded_pssh;
|
||||
drm_content_protection.subelements.push_back(cenc_pssh);
|
||||
}
|
||||
|
||||
representation->AddContentProtectionElement(drm_content_protection);
|
||||
}
|
||||
|
||||
LOG_IF(WARNING, protected_content.content_protection_entry().size() == 0)
|
||||
<< "The media is encrypted but no content protection specified.";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SimpleMpdNotifier::SimpleMpdNotifier(DashProfile dash_profile,
|
||||
const MpdOptions& mpd_options,
|
||||
const std::vector<std::string>& base_urls,
|
||||
|
@ -155,7 +40,7 @@ bool SimpleMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
|||
DCHECK(container_id);
|
||||
|
||||
ContentType content_type = GetContentType(media_info);
|
||||
if (content_type == kUnknown)
|
||||
if (content_type == kContentTypeUnknown)
|
||||
return false;
|
||||
|
||||
base::AutoLock auto_lock(lock_);
|
||||
|
@ -177,11 +62,13 @@ bool SimpleMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
|||
if (representation == NULL)
|
||||
return false;
|
||||
|
||||
// For SimpleMpdNotifier, just put it in Representation. It should still
|
||||
// generate a valid MPD.
|
||||
AddContentProtectionElements(media_info, representation);
|
||||
*container_id = representation->id();
|
||||
|
||||
if (mpd_builder_->type() == MpdBuilder::kStatic)
|
||||
return WriteMpdToFile();
|
||||
return WriteMpdToFile(output_path_, mpd_builder_.get());
|
||||
|
||||
DCHECK(!ContainsKey(representation_map_, representation->id()));
|
||||
representation_map_[representation->id()] = representation;
|
||||
|
@ -212,7 +99,7 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id,
|
|||
return false;
|
||||
}
|
||||
it->second->AddNewSegment(start_time, duration, size);
|
||||
return WriteMpdToFile();
|
||||
return WriteMpdToFile(output_path_, mpd_builder_.get());
|
||||
}
|
||||
|
||||
bool SimpleMpdNotifier::AddContentProtectionElement(
|
||||
|
@ -222,51 +109,4 @@ bool SimpleMpdNotifier::AddContentProtectionElement(
|
|||
return false;
|
||||
}
|
||||
|
||||
SimpleMpdNotifier::ContentType SimpleMpdNotifier::GetContentType(
|
||||
const MediaInfo& media_info) {
|
||||
const bool has_video = media_info.has_video_info();
|
||||
const bool has_audio = media_info.has_audio_info();
|
||||
const bool has_text = media_info.has_text_info();
|
||||
|
||||
if (MoreThanOneTrue(has_video, has_audio, has_text)) {
|
||||
NOTIMPLEMENTED() << "MediaInfo with more than one stream is not supported.";
|
||||
return kUnknown;
|
||||
}
|
||||
if (!AtLeastOneTrue(has_video, has_audio, has_text)) {
|
||||
LOG(ERROR) << "MediaInfo should contain one audio, video, or text stream.";
|
||||
return kUnknown;
|
||||
}
|
||||
return has_video ? kVideo : (has_audio ? kAudio : kText);
|
||||
}
|
||||
|
||||
bool SimpleMpdNotifier::WriteMpdToFile() {
|
||||
CHECK(!output_path_.empty());
|
||||
|
||||
std::string mpd;
|
||||
if (!mpd_builder_->ToString(&mpd)) {
|
||||
LOG(ERROR) << "Failed to write MPD to string.";
|
||||
return false;
|
||||
}
|
||||
|
||||
File* file = File::Open(output_path_.c_str(), "w");
|
||||
if (!file) {
|
||||
LOG(ERROR) << "Failed to open file for writing: " << output_path_;
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* mpd_char_ptr = mpd.data();
|
||||
size_t mpd_bytes_left = mpd.size();
|
||||
while (mpd_bytes_left > 0) {
|
||||
int64_t length = file->Write(mpd_char_ptr, mpd_bytes_left);
|
||||
if (length <= 0) {
|
||||
LOG(ERROR) << "Failed to write to file '" << output_path_ << "' ("
|
||||
<< length << ").";
|
||||
return false;
|
||||
}
|
||||
mpd_char_ptr += length;
|
||||
mpd_bytes_left -= length;
|
||||
}
|
||||
return file->Close();
|
||||
}
|
||||
|
||||
} // namespace edash_packager
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "packager/base/memory/scoped_ptr.h"
|
||||
#include "packager/base/synchronization/lock.h"
|
||||
#include "packager/mpd/base/mpd_notifier.h"
|
||||
#include "packager/mpd/base/mpd_notifier_util.h"
|
||||
|
||||
namespace edash_packager {
|
||||
|
||||
|
@ -64,19 +65,9 @@ class SimpleMpdNotifier : public MpdNotifier {
|
|||
mpd_builder_ = mpd_builder.Pass();
|
||||
}
|
||||
|
||||
enum ContentType {
|
||||
kUnknown,
|
||||
kVideo,
|
||||
kAudio,
|
||||
kText
|
||||
};
|
||||
ContentType GetContentType(const MediaInfo& media_info);
|
||||
bool WriteMpdToFile();
|
||||
|
||||
// MPD output path.
|
||||
std::string output_path_;
|
||||
|
||||
scoped_ptr<MpdBuilder> mpd_builder_;
|
||||
|
||||
base::Lock lock_;
|
||||
|
||||
// [type][lang] = AdaptationSet
|
||||
|
|
|
@ -31,13 +31,15 @@ const char kValidMediaInfo[] =
|
|||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
const uint32_t kDefaultAdaptationSetId = 0u;
|
||||
} // namespace
|
||||
|
||||
class SimpleMpdNotifierTest
|
||||
: public ::testing::TestWithParam<MpdBuilder::MpdType> {
|
||||
protected:
|
||||
SimpleMpdNotifierTest()
|
||||
: default_mock_adaptation_set_(new MockAdaptationSet()) {}
|
||||
: default_mock_adaptation_set_(
|
||||
new MockAdaptationSet(kDefaultAdaptationSetId)) {}
|
||||
|
||||
virtual void SetUp() OVERRIDE {
|
||||
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
|
||||
|
@ -66,7 +68,7 @@ class SimpleMpdNotifierTest
|
|||
}
|
||||
|
||||
// Use output_path_ for specifying the MPD output path so that
|
||||
// SimpleMpdNotifier::WriteMpdToFile() doesn't crash.
|
||||
// WriteMpdToFile() doesn't crash.
|
||||
std::string output_path_;
|
||||
const MpdOptions empty_mpd_option_;
|
||||
const std::vector<std::string> empty_base_urls_;
|
||||
|
|
|
@ -37,10 +37,14 @@
|
|||
'base/bandwidth_estimator.h',
|
||||
'base/content_protection_element.cc',
|
||||
'base/content_protection_element.h',
|
||||
'base/dash_iop_mpd_notifier.cc',
|
||||
'base/dash_iop_mpd_notifier.h',
|
||||
'base/language_utils.cc',
|
||||
'base/language_utils.h',
|
||||
'base/mpd_builder.cc',
|
||||
'base/mpd_builder.h',
|
||||
'base/mpd_notifier_util.cc',
|
||||
'base/mpd_notifier_util.h',
|
||||
'base/mpd_notifier.h',
|
||||
'base/mpd_options.h',
|
||||
'base/mpd_utils.cc',
|
||||
|
@ -68,8 +72,9 @@
|
|||
'type': '<(gtest_target_type)',
|
||||
'sources': [
|
||||
'base/bandwidth_estimator_unittest.cc',
|
||||
'base/mock_mpd_builder.h',
|
||||
'base/dash_iop_mpd_notifier_unittest.cc',
|
||||
'base/mock_mpd_builder.cc',
|
||||
'base/mock_mpd_builder.h',
|
||||
'base/mpd_builder_unittest.cc',
|
||||
'base/simple_mpd_notifier_unittest.cc',
|
||||
'base/xml/xml_node_unittest.cc',
|
||||
|
|
Loading…
Reference in New Issue