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:
Rintaro Kuroiwa 2015-07-21 23:57:21 -07:00
parent aa6f60f9fc
commit c48a94d60f
15 changed files with 1358 additions and 191 deletions

View File

@ -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

View File

@ -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_

View File

@ -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(&notifier, 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(&notifier, 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(&notifier, 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(&notifier, 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(&notifier, 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(&notifier, 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

View File

@ -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,

View File

@ -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.

View File

@ -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) {

View File

@ -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_; }

View File

@ -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()));

View File

@ -12,6 +12,8 @@
#include <stdint.h>
#include "packager/base/macros.h"
namespace edash_packager {
class MediaInfo;

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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_;

View File

@ -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',