Refactor DashIopMpdNotifier Part 2

Move AdaptationSet related functions to the new Period class, which
maps to <Period> element and provides methods to add AdaptationSets.

Change-Id: I0fee290769fbe9a6355cc1b8c86baec8fbc4b4fd
This commit is contained in:
KongQun Yang 2017-12-13 17:00:11 -08:00
parent 99469834e8
commit 718fce068d
22 changed files with 1457 additions and 1183 deletions

View File

@ -12,6 +12,7 @@
#include "packager/base/strings/string_number_conversions.h"
#include "packager/media/base/language_utils.h"
#include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/mpd_options.h"
#include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/representation.h"
#include "packager/mpd/base/xml/xml_node.h"

View File

@ -6,6 +6,9 @@
//
/// All the methods that are virtual are virtual for mocking.
#ifndef PACKAGER_MPD_BASE_ADAPTATION_SET_H_
#define PACKAGER_MPD_BASE_ADAPTATION_SET_H_
#include <stdint.h>
#include <list>
@ -15,7 +18,6 @@
#include <vector>
#include "packager/base/atomic_sequence_num.h"
#include "packager/mpd/base/mpd_options.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
namespace shaka {
@ -24,6 +26,7 @@ class MediaInfo;
class Representation;
struct ContentProtectionElement;
struct MpdOptions;
namespace xml {
class XmlNode;
@ -166,7 +169,7 @@ class AdaptationSet {
AdaptationSet(const AdaptationSet&) = delete;
AdaptationSet& operator=(const AdaptationSet&) = delete;
friend class MpdBuilder;
friend class Period;
friend class AdaptationSetTest;
// kSegmentAlignmentUnknown means that it is uncertain if the
@ -276,3 +279,5 @@ class AdaptationSet {
};
} // namespace shaka
#endif // PACKAGER_MPD_BASE_ADAPTATION_SET_H_

View File

@ -10,6 +10,7 @@
#include <gtest/gtest.h>
#include "packager/mpd/base/content_protection_element.h"
#include "packager/mpd/base/mpd_options.h"
#include "packager/mpd/base/representation.h"
#include "packager/mpd/test/mpd_builder_test_helper.h"
#include "packager/mpd/test/xml_compare.h"

View File

@ -10,36 +10,15 @@
#include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/mpd_notifier_util.h"
#include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/period.h"
#include "packager/mpd/base/representation.h"
namespace shaka {
namespace {
// The easiest way to check whether two protobufs are equal, is to compare the
// serialized version.
bool ProtectedContentEq(
const MediaInfo::ProtectedContent& content_protection1,
const MediaInfo::ProtectedContent& content_protection2) {
return content_protection1.SerializeAsString() ==
content_protection2.SerializeAsString();
}
std::set<std::string> GetUUIDs(
const MediaInfo::ProtectedContent& protected_content) {
std::set<std::string> uuids;
for (int i = 0; i < protected_content.content_protection_entry().size();
++i) {
const MediaInfo::ProtectedContent::ContentProtectionEntry& entry =
protected_content.content_protection_entry(i);
uuids.insert(entry.uuid());
}
return uuids;
}
const bool kContentProtectionInAdaptationSet = true;
} // namespace
namespace shaka {
DashIopMpdNotifier::DashIopMpdNotifier(const MpdOptions& mpd_options)
: MpdNotifier(mpd_options),
output_path_(mpd_options.mpd_params.mpd_output),
@ -63,7 +42,10 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
return false;
base::AutoLock auto_lock(lock_);
AdaptationSet* adaptation_set = GetOrCreateAdaptationSet(media_info);
if (!period_)
period_ = mpd_builder_->AddPeriod();
AdaptationSet* adaptation_set = period_->GetOrCreateAdaptationSet(
media_info, kContentProtectionInAdaptationSet);
DCHECK(adaptation_set);
MediaInfo adjusted_media_info(media_info);
@ -84,7 +66,7 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
bool DashIopMpdNotifier::NotifySampleDuration(uint32_t container_id,
uint32_t sample_duration) {
base::AutoLock auto_lock(lock_);
RepresentationMap::iterator it = representation_map_.find(container_id);
auto it = representation_map_.find(container_id);
if (it == representation_map_.end()) {
LOG(ERROR) << "Unexpected container_id: " << container_id;
return false;
@ -98,7 +80,7 @@ bool DashIopMpdNotifier::NotifyNewSegment(uint32_t container_id,
uint64_t duration,
uint64_t size) {
base::AutoLock auto_lock(lock_);
RepresentationMap::iterator it = representation_map_.find(container_id);
auto it = representation_map_.find(container_id);
if (it == representation_map_.end()) {
LOG(ERROR) << "Unexpected container_id: " << container_id;
return false;
@ -113,7 +95,7 @@ bool DashIopMpdNotifier::NotifyEncryptionUpdate(
const std::vector<uint8_t>& new_key_id,
const std::vector<uint8_t>& new_pssh) {
base::AutoLock auto_lock(lock_);
RepresentationMap::iterator it = representation_map_.find(container_id);
auto it = representation_map_.find(container_id);
if (it == representation_map_.end()) {
LOG(ERROR) << "Unexpected container_id: " << container_id;
return false;
@ -141,127 +123,4 @@ bool DashIopMpdNotifier::Flush() {
return WriteMpdToFile(output_path_, mpd_builder_.get());
}
AdaptationSet* DashIopMpdNotifier::GetOrCreateAdaptationSet(
const MediaInfo& media_info) {
const std::string key = GetAdaptationSetKey(media_info);
std::list<AdaptationSet*>& adaptation_sets = adaptation_set_list_map_[key];
for (AdaptationSet* adaptation_set : adaptation_sets) {
if (protected_adaptation_set_map_.Match(*adaptation_set, media_info))
return adaptation_set;
}
// None of the adaptation sets match with the new content protection.
// Need a new one.
AdaptationSet* new_adaptation_set =
NewAdaptationSet(media_info, adaptation_sets);
if (media_info.has_protected_content()) {
protected_adaptation_set_map_.Register(*new_adaptation_set, media_info);
AddContentProtectionElements(media_info, new_adaptation_set);
// Set adaptation set switching.
for (AdaptationSet* adaptation_set : adaptation_sets) {
if (protected_adaptation_set_map_.Switchable(*adaptation_set,
*new_adaptation_set)) {
new_adaptation_set->AddAdaptationSetSwitching(adaptation_set->id());
adaptation_set->AddAdaptationSetSwitching(new_adaptation_set->id());
}
}
}
adaptation_sets.push_back(new_adaptation_set);
return new_adaptation_set;
}
AdaptationSet* DashIopMpdNotifier::NewAdaptationSet(
const MediaInfo& media_info,
const std::list<AdaptationSet*>& adaptation_sets) {
std::string language = GetLanguage(media_info);
AdaptationSet* new_adaptation_set = mpd_builder_->AddAdaptationSet(language);
if (media_info.has_video_info()) {
// Because 'lang' is ignored for videos, |adaptation_sets| must have
// all the video AdaptationSets.
if (adaptation_sets.size() > 1) {
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
} else if (adaptation_sets.size() == 1) {
// Set "main" Role for both AdaptatoinSets.
(*adaptation_sets.begin())->AddRole(AdaptationSet::kRoleMain);
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
}
if (media_info.video_info().has_playback_rate()) {
uint32_t trick_play_reference_id = 0;
if (!FindOriginalAdaptationSetForTrickPlay(media_info,
&trick_play_reference_id)) {
LOG(ERROR) << "Failed to find main adaptation set for trick play.";
return nullptr;
}
DCHECK_NE(new_adaptation_set->id(), trick_play_reference_id);
new_adaptation_set->AddTrickPlayReferenceId(trick_play_reference_id);
}
} else if (media_info.has_text_info()) {
// IOP requires all AdaptationSets to have (sub)segmentAlignment set to
// true, so carelessly set it to true.
// In practice it doesn't really make sense to adapt between text tracks.
new_adaptation_set->ForceSetSegmentAlignment(true);
}
return new_adaptation_set;
}
bool DashIopMpdNotifier::FindOriginalAdaptationSetForTrickPlay(
const MediaInfo& media_info,
uint32_t* main_adaptation_set_id) {
MediaInfo media_info_no_trickplay = media_info;
media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
std::string key = GetAdaptationSetKey(media_info_no_trickplay);
const std::list<AdaptationSet*>& adaptation_sets =
adaptation_set_list_map_[key];
for (AdaptationSet* adaptation_set : adaptation_sets) {
if (protected_adaptation_set_map_.Match(*adaptation_set, media_info)) {
*main_adaptation_set_id = adaptation_set->id();
return true;
}
}
return false;
}
void DashIopMpdNotifier::ProtectedAdaptationSetMap::Register(
const AdaptationSet& adaptation_set,
const MediaInfo& media_info) {
DCHECK(!ContainsKey(protected_content_map_, adaptation_set.id()));
protected_content_map_[adaptation_set.id()] = media_info.protected_content();
}
bool DashIopMpdNotifier::ProtectedAdaptationSetMap::Match(
const AdaptationSet& adaptation_set,
const MediaInfo& media_info) {
const auto protected_content_it =
protected_content_map_.find(adaptation_set.id());
// If the AdaptationSet ID is not registered in the map, then it is clear
// content.
if (protected_content_it == protected_content_map_.end())
return !media_info.has_protected_content();
if (!media_info.has_protected_content())
return false;
return ProtectedContentEq(protected_content_it->second,
media_info.protected_content());
}
bool DashIopMpdNotifier::ProtectedAdaptationSetMap::Switchable(
const AdaptationSet& adaptation_set_a,
const AdaptationSet& adaptation_set_b) {
const auto protected_content_it_a =
protected_content_map_.find(adaptation_set_a.id());
const auto protected_content_it_b =
protected_content_map_.find(adaptation_set_b.id());
if (protected_content_it_a == protected_content_map_.end())
return protected_content_it_b == protected_content_map_.end();
if (protected_content_it_b == protected_content_map_.end())
return false;
// Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the
// same UUIDs then those are switchable.
return GetUUIDs(protected_content_it_a->second) ==
GetUUIDs(protected_content_it_b->second);
}
} // namespace shaka

View File

@ -9,7 +9,6 @@
#include "packager/mpd/base/mpd_notifier.h"
#include <list>
#include <map>
#include <string>
#include <vector>
@ -22,6 +21,7 @@ namespace shaka {
class AdaptationSet;
class MpdBuilder;
class Period;
class Representation;
/// This class is an MpdNotifier which will try its best to generate a
@ -59,31 +59,6 @@ class DashIopMpdNotifier : public MpdNotifier {
private:
friend class DashIopMpdNotifierTest;
// Maps representation ID to Representation.
typedef std::map<uint32_t, Representation*> RepresentationMap;
// 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* GetOrCreateAdaptationSet(const MediaInfo& media_info);
// Helper function to get a new AdaptationSet and set new AdaptationSet
// attributes.
AdaptationSet* NewAdaptationSet(
const MediaInfo& media_info,
const std::list<AdaptationSet*>& adaptation_sets);
// Gets the original AdaptationSet which the trick play video belongs
// to and returns the id of the original adapatation set.
// It is assumed that the corresponding AdaptationSet has been created before
// the trick play AdaptationSet.
// Returns true if main_adaptation_id is found, otherwise false;
bool FindOriginalAdaptationSetForTrickPlay(
const MediaInfo& media_info,
uint32_t* original_adaptation_set_id);
// Testing only method. Returns a pointer to MpdBuilder.
MpdBuilder* MpdBuilderForTesting() const {
return mpd_builder_.get();
@ -94,39 +69,14 @@ class DashIopMpdNotifier : public MpdNotifier {
mpd_builder_ = std::move(mpd_builder);
}
std::map<std::string, std::list<AdaptationSet*>> adaptation_set_list_map_;
RepresentationMap representation_map_;
// Tracks ProtectedContent in AdaptationSet.
class ProtectedAdaptationSetMap {
public:
ProtectedAdaptationSetMap() = default;
// Register the |adaptation_set| with associated |media_info| in the map.
void Register(const AdaptationSet& adaptation_set,
const MediaInfo& media_info);
// Check if the protected content associated with |adaptation_set| matches
// with the one in |media_info|.
bool Match(const AdaptationSet& adaptation_set,
const MediaInfo& media_info);
// Check if the two adaptation sets are switchable.
bool Switchable(const AdaptationSet& adaptation_set_a,
const AdaptationSet& adaptation_set_b);
private:
ProtectedAdaptationSetMap(const ProtectedAdaptationSetMap&) = delete;
ProtectedAdaptationSetMap& operator=(const ProtectedAdaptationSetMap&) =
delete;
// Maps AdaptationSet ID to ProtectedContent.
std::map<uint32_t, MediaInfo::ProtectedContent> protected_content_map_;
};
ProtectedAdaptationSetMap protected_adaptation_set_map_;
// MPD output path.
std::string output_path_;
std::unique_ptr<MpdBuilder> mpd_builder_;
Period* period_ = nullptr; // owned by |mpd_builder_|.
base::Lock lock_;
// Maps representation ID to Representation.
std::map<uint32_t, Representation*> representation_map_;
// Maps Representation ID to AdaptationSet. This is for updating the PSSH.
std::map<uint32_t, AdaptationSet*> representation_id_to_adaptation_set_;
};

View File

@ -5,6 +5,7 @@
// https://developers.google.com/open-source/licenses/bsd
#include <gmock/gmock.h>
#include <google/protobuf/util/message_differencer.h>
#include <gtest/gtest.h>
#include "packager/base/files/file_path.h"
@ -17,70 +18,19 @@
namespace shaka {
using ::testing::_;
using ::testing::AnyOf;
using ::testing::Eq;
using ::testing::ElementsAre;
using ::testing::InSequence;
using ::testing::Return;
using ::testing::StrEq;
using ::testing::UnorderedElementsAre;
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 bool kContentProtectionInAdaptationSet = true;
bool ElementEqual(const Element& lhs, const 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 ContentProtectionElement& lhs,
const 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);
MATCHER_P(EqualsProto, message, "") {
return ::google::protobuf::util::MessageDifferencer::Equals(arg, message);
}
} // namespace
@ -94,7 +44,8 @@ MATCHER_P(ContentProtectionElementEq, expected, "") {
class DashIopMpdNotifierTest : public ::testing::Test {
protected:
DashIopMpdNotifierTest()
: default_mock_adaptation_set_(
: default_mock_period_(new MockPeriod),
default_mock_adaptation_set_(
new MockAdaptationSet(kDefaultAdaptationSetId)),
default_mock_representation_(
new MockRepresentation(kDefaultRepresentationId)) {}
@ -102,6 +53,24 @@ class DashIopMpdNotifierTest : public ::testing::Test {
void SetUp() override {
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
empty_mpd_option_.mpd_params.mpd_output = temp_file_path_.AsUTF8Unsafe();
// Three valid media info. The actual data does not matter.
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";
valid_media_info1_ = ConvertToMediaInfo(kValidMediaInfo);
valid_media_info2_ = valid_media_info1_;
valid_media_info2_.mutable_video_info()->set_width(960);
valid_media_info3_ = valid_media_info1_;
valid_media_info3_.mutable_video_info()->set_width(480);
}
void TearDown() override {
@ -113,16 +82,23 @@ class DashIopMpdNotifierTest : public ::testing::Test {
notifier->SetMpdBuilderForTesting(std::move(mpd_builder));
}
protected:
// Empty mpd options except with output path specified, so that
// WriteMpdToFile() doesn't crash.
MpdOptions empty_mpd_option_;
// Default mocks that can be used for the tests.
// IOW, if a test only requires one instance of
// Mock{AdaptationSet,Representation}, these can be used.
// Mock{Period,AdaptationSet,Representation}, these can be used.
std::unique_ptr<MockPeriod> default_mock_period_;
std::unique_ptr<MockAdaptationSet> default_mock_adaptation_set_;
std::unique_ptr<MockRepresentation> default_mock_representation_;
// Three valid media info. The actual content does not matter.
MediaInfo valid_media_info1_;
MediaInfo valid_media_info2_;
MediaInfo valid_media_info3_;
private:
base::FilePath temp_file_path_;
};
@ -134,9 +110,12 @@ TEST_F(DashIopMpdNotifierTest, NotifyNewContainer) {
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_,
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_),
Eq(kContentProtectionInAdaptationSet)))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRole(_)).Times(0);
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(default_mock_representation_.get()));
@ -146,616 +125,48 @@ TEST_F(DashIopMpdNotifierTest, NotifyNewContainer) {
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&unused_container_id));
EXPECT_TRUE(
notifier.NotifyNewContainer(valid_media_info1_, &unused_container_id));
EXPECT_TRUE(notifier.Flush());
}
// Verify that basic VOD NotifyNewContainer() operation works on trick play
// streams.
// No encrypted contents.
TEST_F(DashIopMpdNotifierTest, NotifyNewContainerForTrickPlay) {
const char kTrickPlayMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 10\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
" playback_rate: 10\n"
"}\n"
"container_type: 1\n";
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
// Not using default mocks in this test so that we can keep track of
// mocks by named mocks.
const uint32_t kAdaptationSetId = 2u;
const uint32_t kTrickPlayAdaptationSetId = 3u;
std::unique_ptr<MockAdaptationSet> mock_adaptation_set(
new MockAdaptationSet(kAdaptationSetId));
std::unique_ptr<MockAdaptationSet> mock_tp_adaptation_set(
new MockAdaptationSet(kTrickPlayAdaptationSetId));
const uint32_t kRepresentationId = 4u;
const uint32_t kTrickPlayRepresentationId = 5u;
std::unique_ptr<MockRepresentation> mock_representation(
new MockRepresentation(kRepresentationId));
std::unique_ptr<MockRepresentation> mock_tp_representation(
new MockRepresentation(kTrickPlayRepresentationId));
InSequence in_sequence;
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
.WillOnce(Return(mock_adaptation_set.get()));
EXPECT_CALL(*mock_adaptation_set, AddRole(_)).Times(0);
EXPECT_CALL(*mock_adaptation_set, AddRepresentation(_))
.WillOnce(Return(mock_representation.get()));
// Calls for trick-play stream.
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
.WillOnce(Return(mock_tp_adaptation_set.get()));
EXPECT_CALL(*mock_tp_adaptation_set, AddRole(_)).Times(0);
EXPECT_CALL(*mock_tp_adaptation_set,
AddTrickPlayReferenceId(kAdaptationSetId))
.Times(1);
EXPECT_CALL(*mock_tp_adaptation_set, AddRepresentation(_))
.WillOnce(Return(mock_tp_representation.get()));
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kTrickPlayMediaInfo), &unused_container_id));
EXPECT_TRUE(notifier.Flush());
}
// Verify that if the MediaInfo contains text information, then
// MpdBuilder::ForceSetSegmentAlignment() is called.
TEST_F(DashIopMpdNotifierTest, NotifyNewTextContainer) {
const char kTextMediaInfo[] =
"text_info {\n"
" format: 'ttml'\n"
" language: 'en'\n"
"}\n"
"container_type: CONTAINER_TEXT\n";
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(StrEq("en")))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRole(_)).Times(0);
EXPECT_CALL(*default_mock_adaptation_set_, ForceSetSegmentAlignment(true));
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(default_mock_representation_.get()));
// This is for the Flush() below but adding expectation here because the next
// std::move(lines) the pointer.
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kTextMediaInfo),
&unused_container_id));
EXPECT_TRUE(notifier.Flush());
}
// Verify VOD NotifyNewContainer() operation works with different
// MediaInfo::ProtectedContent.
// Two AdaptationSets should be created.
// AdaptationSets with different DRM won't be switchable.
TEST_F(DashIopMpdNotifierTest,
NotifyNewContainersWithDifferentProtectedContent) {
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
// 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;
std::unique_ptr<MockAdaptationSet> sd_adaptation_set(
new MockAdaptationSet(kSdAdaptationSetId));
std::unique_ptr<MockAdaptationSet> hd_adaptation_set(
new MockAdaptationSet(kHdAdaptationSetId));
const uint32_t kSdRepresentation = 4u;
const uint32_t kHdRepresentation = 5u;
std::unique_ptr<MockRepresentation> sd_representation(
new MockRepresentation(kSdRepresentation));
std::unique_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()));
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
.WillOnce(Return(hd_adaptation_set.get()));
// Add main Role here for both.
EXPECT_CALL(*sd_adaptation_set, AddRole(AdaptationSet::kRoleMain));
EXPECT_CALL(*hd_adaptation_set, AddRole(AdaptationSet::kRoleMain));
// 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()));
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
EXPECT_THAT(sd_adaptation_set->adaptation_set_switching_ids(), ElementsAre());
EXPECT_THAT(hd_adaptation_set->adaptation_set_switching_ids(), ElementsAre());
}
// Verify VOD NotifyNewContainer() operation works with same
// MediaInfo::ProtectedContent. Only one AdaptationSet should be
// created.
TEST_F(DashIopMpdNotifierTest, NotifyNewContainersWithSameProtectedContent) {
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
// 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;
std::unique_ptr<MockRepresentation> sd_representation(
new MockRepresentation(kSdRepresentation));
std::unique_ptr<MockRepresentation> hd_representation(
new MockRepresentation(kHdRepresentation));
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_, AddRole(_)).Times(0);
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(sd_representation.get()));
// 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_, AddRole(_)).Times(0);
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(hd_representation.get()));
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
// No adaptation set switching if there is only one AdaptationSet.
EXPECT_THAT(default_mock_adaptation_set_->adaptation_set_switching_ids(),
ElementsAre());
}
// AddContentProtection() should not work and should always return false.
TEST_F(DashIopMpdNotifierTest, AddContentProtection) {
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(default_mock_representation_.get()));
uint32_t container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &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
// the AdaptationSet they belong to should be switchable.
// 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. SD AdaptationSet and HD AdaptationSet
// should be switchable.
// 3. Add a 4k protected content. This should also make a new AdaptationSet.
// It should be switchable with SD/HD AdaptationSet.
TEST_F(DashIopMpdNotifierTest, SetAdaptationSetSwitching) {
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
// 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;
std::unique_ptr<MockAdaptationSet> sd_adaptation_set(
new MockAdaptationSet(kSdAdaptationSetId));
std::unique_ptr<MockAdaptationSet> hd_adaptation_set(
new MockAdaptationSet(kHdAdaptationSetId));
const uint32_t kSdRepresentation = 4u;
const uint32_t kHdRepresentation = 5u;
std::unique_ptr<MockRepresentation> sd_representation(
new MockRepresentation(kSdRepresentation));
std::unique_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()));
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()));
// 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, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
EXPECT_THAT(sd_adaptation_set->adaptation_set_switching_ids(),
ElementsAre(kHdAdaptationSetId));
EXPECT_THAT(hd_adaptation_set->adaptation_set_switching_ids(),
ElementsAre(kSdAdaptationSetId));
// Add another content that has the same protected content and make sure that
// adaptation set switching is set correctly.
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;
std::unique_ptr<MockAdaptationSet> fourk_adaptation_set(
new MockAdaptationSet(k4kAdaptationSetId));
const uint32_t k4kRepresentationId = 4001u;
std::unique_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()));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(k4kProtectedContent), &unused_container_id));
EXPECT_THAT(sd_adaptation_set->adaptation_set_switching_ids(),
UnorderedElementsAre(kHdAdaptationSetId, k4kAdaptationSetId));
EXPECT_THAT(hd_adaptation_set->adaptation_set_switching_ids(),
UnorderedElementsAre(kSdAdaptationSetId, k4kAdaptationSetId));
EXPECT_THAT(fourk_adaptation_set->adaptation_set_switching_ids(),
ElementsAre(kSdAdaptationSetId, kHdAdaptationSetId));
}
// Even if the UUIDs match, video and audio AdaptationSets should not be
// switchable.
TEST_F(DashIopMpdNotifierTest,
DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
// 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 kVideoAdaptationSetId = 6u;
const uint32_t kAudioAdaptationSetId = 7u;
std::unique_ptr<MockAdaptationSet> video_adaptation_set(
new MockAdaptationSet(kVideoAdaptationSetId));
std::unique_ptr<MockAdaptationSet> audio_adaptation_set(
new MockAdaptationSet(kAudioAdaptationSetId));
const uint32_t kVideoRepresentation = 8u;
const uint32_t kAudioRepresentation = 9u;
std::unique_ptr<MockRepresentation> video_representation(
new MockRepresentation(kVideoRepresentation));
std::unique_ptr<MockRepresentation> audio_representation(
new MockRepresentation(kAudioRepresentation));
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, AddRole(_)).Times(0);
EXPECT_CALL(*video_adaptation_set, AddRepresentation(_))
.WillOnce(Return(video_representation.get()));
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, AddRole(_)).Times(0);
EXPECT_CALL(*audio_adaptation_set, AddRepresentation(_))
.WillOnce(Return(audio_representation.get()));
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kVideoContent), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent), &unused_container_id));
EXPECT_THAT(video_adaptation_set->adaptation_set_switching_ids(),
ElementsAre());
EXPECT_THAT(audio_adaptation_set->adaptation_set_switching_ids(),
ElementsAre());
}
TEST_F(DashIopMpdNotifierTest, UpdateEncryption) {
const char kProtectedContent[] =
"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";
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRole(_)).Times(0);
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(default_mock_representation_.get()));
uint32_t container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kProtectedContent),
&container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
::testing::Mock::VerifyAndClearExpectations(
default_mock_adaptation_set_.get());
@ -783,73 +194,19 @@ TEST_F(DashIopMpdNotifierTest, UpdateEncryption) {
TEST_F(DashIopMpdNotifierTest, NotifyNewContainerAndSampleDurationNoMock) {
DashIopMpdNotifier notifier(empty_mpd_option_);
uint32_t container_id;
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
const uint32_t kAnySampleDuration = 1000;
EXPECT_TRUE(notifier.NotifySampleDuration(container_id, kAnySampleDuration));
EXPECT_TRUE(notifier.Flush());
}
// Don't put different audio languages or codecs in the same AdaptationSet.
TEST_F(DashIopMpdNotifierTest, SplitAdaptationSetsByLanguageAndCodec) {
// MP4, English
const char kAudioContent1[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
// MP4, German
const char kAudioContent2[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
// WebM, German
const char kAudioContent3[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
// WebM, German again
const char kAudioContent4[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
// Test multiple media info with some belongs to the same AdaptationSets.
TEST_F(DashIopMpdNotifierTest, MultipleMediaInfo) {
DashIopMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
std::unique_ptr<MockAdaptationSet> adaptation_set1(new MockAdaptationSet(1));
std::unique_ptr<MockAdaptationSet> adaptation_set2(new MockAdaptationSet(2));
std::unique_ptr<MockAdaptationSet> adaptation_set3(new MockAdaptationSet(3));
std::unique_ptr<MockRepresentation> representation1(
new MockRepresentation(1));
@ -857,35 +214,39 @@ TEST_F(DashIopMpdNotifierTest, SplitAdaptationSetsByLanguageAndCodec) {
new MockRepresentation(2));
std::unique_ptr<MockRepresentation> representation3(
new MockRepresentation(3));
std::unique_ptr<MockRepresentation> representation4(
new MockRepresentation(4));
// We expect three AdaptationSets.
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
.WillOnce(Return(adaptation_set1.get()))
.WillOnce(Return(adaptation_set2.get()))
.WillOnce(Return(adaptation_set3.get()));
// The first AdaptationSet should have Eng MP4, one Representation.
EXPECT_CALL(*adaptation_set1, AddRepresentation(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_,
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
.WillOnce(Return(adaptation_set1.get()));
EXPECT_CALL(*adaptation_set1,
AddRepresentation(EqualsProto(valid_media_info1_)))
.WillOnce(Return(representation1.get()));
// The second AdaptationSet should have Ger MP4, one Representation.
EXPECT_CALL(*adaptation_set2, AddRepresentation(_))
.WillOnce(Return(representation2.get()));
// The third AdaptationSet should have Ger WebM, two Representations.
EXPECT_CALL(*adaptation_set3, AddRepresentation(_))
.WillOnce(Return(representation3.get()))
.WillOnce(Return(representation4.get()));
// Return the same adaptation set for |valid_media_info2_| and
// |valid_media_info3_|. This results in AddRepresentation to be called twice
// on |adaptation_set2|.
EXPECT_CALL(*default_mock_period_,
GetOrCreateAdaptationSet(AnyOf(EqualsProto(valid_media_info2_),
EqualsProto(valid_media_info3_)),
_))
.WillOnce(Return(adaptation_set2.get()))
.WillOnce(Return(adaptation_set2.get()));
EXPECT_CALL(*adaptation_set2,
AddRepresentation(AnyOf(EqualsProto(valid_media_info2_),
EqualsProto(valid_media_info3_))))
.WillOnce(Return(representation2.get()))
.WillOnce(Return(representation3.get()));
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent1), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent2), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent3), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent4), &unused_container_id));
EXPECT_TRUE(
notifier.NotifyNewContainer(valid_media_info1_, &unused_container_id));
EXPECT_TRUE(
notifier.NotifyNewContainer(valid_media_info2_, &unused_container_id));
EXPECT_TRUE(
notifier.NotifyNewContainer(valid_media_info3_, &unused_container_id));
}
} // namespace shaka

View File

@ -13,6 +13,9 @@ const MpdOptions kDefaultMpdOptions;
MockMpdBuilder::MockMpdBuilder() : MpdBuilder(kDefaultMpdOptions) {}
MockMpdBuilder::~MockMpdBuilder() {}
MockPeriod::MockPeriod()
: Period(kDefaultMpdOptions, &sequence_counter_, &sequence_counter_) {}
MockAdaptationSet::MockAdaptationSet(uint32_t adaptation_set_id)
: AdaptationSet(adaptation_set_id,
kEmptyLang,

View File

@ -14,6 +14,7 @@
#include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/content_protection_element.h"
#include "packager/mpd/base/mpd_builder.h"
#include "packager/mpd/base/period.h"
#include "packager/mpd/base/representation.h"
namespace shaka {
@ -23,10 +24,23 @@ class MockMpdBuilder : public MpdBuilder {
MockMpdBuilder();
~MockMpdBuilder() override;
MOCK_METHOD1(AddAdaptationSet, AdaptationSet*(const std::string& lang));
MOCK_METHOD0(AddPeriod, Period*());
MOCK_METHOD1(ToString, bool(std::string* output));
};
class MockPeriod : public Period {
public:
MockPeriod();
MOCK_METHOD2(GetOrCreateAdaptationSet,
AdaptationSet*(const MediaInfo& media_info,
bool content_protection_in_adaptation_set));
private:
// Only for constructing the super class. Not used for testing.
base::AtomicSequenceNumber sequence_counter_;
};
class MockAdaptationSet : public AdaptationSet {
public:
// |adaptation_set_id| is the id for the AdaptationSet.

View File

@ -15,6 +15,7 @@
#include "packager/base/time/time.h"
#include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/period.h"
#include "packager/mpd/base/xml/xml_node.h"
#include "packager/version/version.h"
@ -139,18 +140,10 @@ void MpdBuilder::AddBaseUrl(const std::string& base_url) {
base_urls_.push_back(base_url);
}
AdaptationSet* MpdBuilder::AddAdaptationSet(const std::string& lang) {
std::unique_ptr<AdaptationSet> adaptation_set(
new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
&representation_counter_));
DCHECK(adaptation_set);
if (!lang.empty() && lang == mpd_options_.mpd_params.default_language) {
adaptation_set->AddRole(AdaptationSet::kRoleMain);
}
adaptation_sets_.push_back(std::move(adaptation_set));
return adaptation_sets_.back().get();
Period* MpdBuilder::AddPeriod() {
periods_.emplace_back(new Period(mpd_options_, &adaptation_set_counter_,
&representation_counter_));
return periods_.back().get();
}
bool MpdBuilder::ToString(std::string* output) {
@ -158,7 +151,7 @@ bool MpdBuilder::ToString(std::string* output) {
static LibXmlInitializer lib_xml_initializer;
xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
if (!doc.get())
if (!doc)
return false;
static const int kNiceFormat = 1;
@ -180,39 +173,21 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
XmlNode mpd("MPD");
// Iterate thru AdaptationSets and add them to one big Period element.
XmlNode period("Period");
// Always set id=0 for now. Since this class can only generate one Period
// at the moment, just use a constant.
// Required for 'dynamic' MPDs.
period.SetId(0);
for (const std::unique_ptr<AdaptationSet>& adaptation_set :
adaptation_sets_) {
xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
if (!child.get() || !period.AddChild(std::move(child)))
return NULL;
}
// Add baseurls to MPD.
std::list<std::string>::const_iterator base_urls_it = base_urls_.begin();
for (; base_urls_it != base_urls_.end(); ++base_urls_it) {
XmlNode base_url("BaseURL");
base_url.SetContent(*base_urls_it);
for (const std::string& base_url : base_urls_) {
XmlNode xml_base_url("BaseURL");
xml_base_url.SetContent(base_url);
if (!mpd.AddChild(base_url.PassScopedPtr()))
return NULL;
if (!mpd.AddChild(xml_base_url.PassScopedPtr()))
return nullptr;
}
// TODO(kqyang): Should we set @start unconditionally to 0?
if (mpd_options_.mpd_type == MpdType::kDynamic) {
// This is the only Period and it is a regular period.
period.SetStringAttribute("start", "PT0S");
for (const auto& period : periods_) {
xml::scoped_xml_ptr<xmlNode> period_node(period->GetXml());
if (!period_node || !mpd.AddChild(std::move(period_node)))
return nullptr;
}
if (!mpd.AddChild(period.PassScopedPtr()))
return NULL;
AddMpdNameSpaceInfo(&mpd);
static const char kOnDemandProfile[] =
@ -331,15 +306,21 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
DCHECK(mpd_node);
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
xmlNodePtr period_node = FindPeriodNode(mpd_node);
DCHECK(period_node) << "Period element must be a child of mpd_node.";
DCHECK(IsPeriodNode(period_node));
// TODO(kqyang): Verify if this works for static + live profile.
// Attribute mediaPresentationDuration must be present for 'static' MPD. So
// setting "PT0S" is required even if none of the representaions have duration
// attribute.
float max_duration = 0.0f;
xmlNodePtr period_node = FindPeriodNode(mpd_node);
if (!period_node) {
LOG(WARNING) << "No Period node found. Set MPD duration to 0.";
return 0.0f;
}
DCHECK(IsPeriodNode(period_node));
// TODO(kqyang): Why don't we iterate the C++ classes instead of iterating XML
// elements?
// TODO(kqyang): Verify if this works for static + live profile.
for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
@ -361,21 +342,8 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds);
double earliest_timestamp(-1);
for (const std::unique_ptr<AdaptationSet>& adaptation_set :
adaptation_sets_) {
double timestamp;
if (adaptation_set->GetEarliestTimestamp(&timestamp) &&
((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
earliest_timestamp = timestamp;
}
}
if (earliest_timestamp < 0)
return false;
*timestamp_seconds = earliest_timestamp;
return true;
DCHECK(!periods_.empty());
return periods_.front()->GetEarliestTimestamp(timestamp_seconds);
}
void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,

View File

@ -28,6 +28,7 @@ namespace shaka {
class AdaptationSet;
class MediaInfo;
class Period;
namespace xml {
class XmlNode;
@ -45,11 +46,9 @@ class MpdBuilder {
/// @param base_url URL for <BaseURL> entry.
void AddBaseUrl(const std::string& base_url);
/// Adds <AdaptationSet> to the MPD.
/// @param lang is the language of the AdaptationSet. This can be empty for
/// videos, for example.
/// @return The new adaptation set, which is owned by this instance.
virtual AdaptationSet* AddAdaptationSet(const std::string& lang);
/// Adds <Period> to the MPD.
/// @return The new period, which is owned by this instance.
virtual Period* AddPeriod();
/// Writes the MPD to the given string.
/// @param[out] output is an output string where the MPD gets written.
@ -108,7 +107,7 @@ class MpdBuilder {
bool GetEarliestTimestamp(double* timestamp_seconds);
MpdOptions mpd_options_;
std::list<std::unique_ptr<AdaptationSet>> adaptation_sets_;
std::list<std::unique_ptr<Period>> periods_;
std::list<std::string> base_urls_;
std::string availability_start_time_;

View File

@ -8,6 +8,7 @@
#include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/mpd_builder.h"
#include "packager/mpd/base/period.h"
#include "packager/mpd/test/mpd_builder_test_helper.h"
#include "packager/version/version.h"
@ -35,6 +36,11 @@ class MpdBuilderTest : public ::testing::Test {
}
~MpdBuilderTest() override {}
void SetUp() override {
period_ = mpd_.AddPeriod();
ASSERT_TRUE(period_);
}
MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; }
void CheckMpd(const std::string& expected_output_file) {
@ -50,7 +56,8 @@ class MpdBuilderTest : public ::testing::Test {
// Creates a new AdaptationSet and adds a Representation element using
// |media_info|.
void AddRepresentation(const MediaInfo& media_info) {
AdaptationSet* adaptation_set = mpd_.AddAdaptationSet("");
AdaptationSet* adaptation_set =
period_->GetOrCreateAdaptationSet(media_info, true);
ASSERT_TRUE(adaptation_set);
Representation* representation =
@ -66,6 +73,7 @@ class MpdBuilderTest : public ::testing::Test {
Representation* representation_; // Owned by |mpd_|.
private:
Period* period_ = nullptr;
base::AtomicSequenceNumber representation_counter_;
DISALLOW_COPY_AND_ASSIGN(MpdBuilderTest);
@ -110,13 +118,11 @@ TEST_F(OnDemandMpdBuilderTest, Video) {
}
TEST_F(OnDemandMpdBuilderTest, TwoVideosWithDifferentResolutions) {
AdaptationSet* adaptation_set = mpd_.AddAdaptationSet("");
MediaInfo media_info1 = GetTestMediaInfo(kFileNameVideoMediaInfo1);
ASSERT_TRUE(adaptation_set->AddRepresentation(media_info1));
MediaInfo media_info2 = GetTestMediaInfo(kFileNameVideoMediaInfo2);
ASSERT_TRUE(adaptation_set->AddRepresentation(media_info2));
// The order matters here to check against expected output.
ASSERT_NO_FATAL_FAILURE(AddRepresentation(media_info1));
ASSERT_NO_FATAL_FAILURE(AddRepresentation(media_info2));
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputVideo1And2));
}
@ -125,21 +131,9 @@ TEST_F(OnDemandMpdBuilderTest, TwoVideosWithDifferentResolutions) {
TEST_F(OnDemandMpdBuilderTest, VideoAndAudio) {
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
MediaInfo audio_media_info = GetTestMediaInfo(kFileNameAudioMediaInfo1);
// The order matters here to check against expected output.
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(audio_adaptation_set);
Representation* audio_representation =
audio_adaptation_set->AddRepresentation(audio_media_info);
ASSERT_TRUE(audio_representation);
Representation* video_representation =
video_adaptation_set->AddRepresentation(video_media_info);
ASSERT_TRUE(video_representation);
ASSERT_NO_FATAL_FAILURE(AddRepresentation(video_media_info));
ASSERT_NO_FATAL_FAILURE(AddRepresentation(audio_media_info));
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputAudio1AndVideo1));
}
@ -161,24 +155,23 @@ TEST_F(OnDemandMpdBuilderTest, MediaInfoMissingBandwidth) {
TEST_F(LiveMpdBuilderTest, DynamicCheckMpdAttributes) {
static const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!--Generated with https://github.com/google/shaka-packager "
"version <tag>-<hash>-<test>-->\n"
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" "
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"xsi:schemaLocation="
"\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\" "
"xmlns:cenc=\"urn:mpeg:cenc:2013\" "
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"minBufferTime=\"PT2S\" "
"type=\"dynamic\" "
"publishTime=\"2016-01-11T15:10:24Z\" "
"availabilityStartTime=\"2011-12-25T12:30:00\">\n"
" <Period id=\"0\" start=\"PT0S\"/>\n"
"</MPD>\n";
"<!--Generated with https://github.com/google/shaka-packager"
" version <tag>-<hash>-<test>-->\n"
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
" xmlns:xlink=\"http://www.w3.org/1999/xlink\""
" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\""
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\""
" minBufferTime=\"PT2S\""
" type=\"dynamic\""
" publishTime=\"2016-01-11T15:10:24Z\""
" availabilityStartTime=\"2011-12-25T12:30:00\""
" minimumUpdatePeriod=\"PT2S\"/>\n";
std::string mpd_doc;
mutable_mpd_options()->mpd_type = MpdType::kDynamic;
mutable_mpd_options()->mpd_params.minimum_update_period = 2;
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
ASSERT_EQ(kExpectedOutput, mpd_doc);
}
@ -186,20 +179,17 @@ TEST_F(LiveMpdBuilderTest, DynamicCheckMpdAttributes) {
TEST_F(LiveMpdBuilderTest, StaticCheckMpdAttributes) {
static const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!--Generated with https://github.com/google/shaka-packager "
"version <tag>-<hash>-<test>-->\n"
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" "
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"xsi:schemaLocation="
"\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\" "
"xmlns:cenc=\"urn:mpeg:cenc:2013\" "
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"minBufferTime=\"PT2S\" "
"type=\"static\" "
"mediaPresentationDuration=\"PT0S\">\n"
" <Period id=\"0\"/>\n"
"</MPD>\n";
"<!--Generated with https://github.com/google/shaka-packager"
" version <tag>-<hash>-<test>-->\n"
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
" xmlns:xlink=\"http://www.w3.org/1999/xlink\""
" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\""
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\""
" minBufferTime=\"PT2S\""
" type=\"static\""
" mediaPresentationDuration=\"PT0S\"/>\n";
std::string mpd_doc;
mutable_mpd_options()->mpd_type = MpdType::kStatic;

237
packager/mpd/base/period.cc Normal file
View File

@ -0,0 +1,237 @@
// Copyright 2017 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/period.h"
#include "packager/base/stl_util.h"
#include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/mpd_options.h"
#include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/xml/xml_node.h"
namespace shaka {
namespace {
// The easiest way to check whether two protobufs are equal, is to compare the
// serialized version.
bool ProtectedContentEq(
const MediaInfo::ProtectedContent& content_protection1,
const MediaInfo::ProtectedContent& content_protection2) {
return content_protection1.SerializeAsString() ==
content_protection2.SerializeAsString();
}
std::set<std::string> GetUUIDs(
const MediaInfo::ProtectedContent& protected_content) {
std::set<std::string> uuids;
for (const auto& entry : protected_content.content_protection_entry())
uuids.insert(entry.uuid());
return uuids;
}
} // namespace
Period::Period(const MpdOptions& mpd_options,
base::AtomicSequenceNumber* adaptation_set_counter,
base::AtomicSequenceNumber* representation_counter)
: mpd_options_(mpd_options),
adaptation_set_counter_(adaptation_set_counter),
representation_counter_(representation_counter) {}
AdaptationSet* Period::GetOrCreateAdaptationSet(
const MediaInfo& media_info,
bool content_protection_in_adaptation_set) {
// AdaptationSets with the same key should only differ in ContentProtection,
// which also means that if |content_protection_in_adaptation_set| is false,
// there should be at most one entry in |adaptation_sets|.
const std::string key = GetAdaptationSetKey(media_info);
std::list<AdaptationSet*>& adaptation_sets = adaptation_set_list_map_[key];
if (content_protection_in_adaptation_set) {
for (AdaptationSet* adaptation_set : adaptation_sets) {
if (protected_adaptation_set_map_.Match(*adaptation_set, media_info))
return adaptation_set;
}
} else {
if (!adaptation_sets.empty()) {
DCHECK_EQ(adaptation_sets.size(), 1u);
return adaptation_sets.front();
}
}
// None of the adaptation sets match with the new content protection.
// Need a new one.
std::string language = GetLanguage(media_info);
std::unique_ptr<AdaptationSet> new_adaptation_set =
NewAdaptationSet(adaptation_set_counter_->GetNext(), language,
mpd_options_, representation_counter_);
if (!SetNewAdaptationSetAttributes(language, media_info, adaptation_sets,
new_adaptation_set.get())) {
return nullptr;
}
if (content_protection_in_adaptation_set &&
media_info.has_protected_content()) {
protected_adaptation_set_map_.Register(*new_adaptation_set, media_info);
AddContentProtectionElements(media_info, new_adaptation_set.get());
for (AdaptationSet* adaptation_set : adaptation_sets) {
if (protected_adaptation_set_map_.Switchable(*adaptation_set,
*new_adaptation_set)) {
new_adaptation_set->AddAdaptationSetSwitching(adaptation_set->id());
adaptation_set->AddAdaptationSetSwitching(new_adaptation_set->id());
}
}
}
adaptation_sets.push_back(new_adaptation_set.get());
adaptation_sets_.push_back(std::move(new_adaptation_set));
return adaptation_sets_.back().get();
}
xml::scoped_xml_ptr<xmlNode> Period::GetXml() {
xml::XmlNode period("Period");
// Always set id=0 for now.
// Required for 'dynamic' MPDs.
period.SetId(0);
// Iterate thru AdaptationSets and add them to one big Period element.
for (const auto& adaptation_set : adaptation_sets_) {
xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
if (!child || !period.AddChild(std::move(child)))
return nullptr;
}
// TODO(kqyang): Should we set @start unconditionally to 0?
if (mpd_options_.mpd_type == MpdType::kDynamic) {
// This is the only Period and it is a regular period.
period.SetStringAttribute("start", "PT0S");
}
return period.PassScopedPtr();
}
bool Period::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds);
double earliest_timestamp(-1);
for (const std::unique_ptr<AdaptationSet>& adaptation_set :
adaptation_sets_) {
double timestamp;
if (adaptation_set->GetEarliestTimestamp(&timestamp) &&
(earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
DCHECK_GE(timestamp, 0);
earliest_timestamp = timestamp;
}
}
if (earliest_timestamp < 0)
return false;
*timestamp_seconds = earliest_timestamp;
return true;
}
std::unique_ptr<AdaptationSet> Period::NewAdaptationSet(
uint32_t adaptation_set_id,
const std::string& language,
const MpdOptions& options,
base::AtomicSequenceNumber* representation_counter) {
return std::unique_ptr<AdaptationSet>(new AdaptationSet(
adaptation_set_id, language, options, representation_counter));
}
bool Period::SetNewAdaptationSetAttributes(
const std::string& language,
const MediaInfo& media_info,
const std::list<AdaptationSet*>& adaptation_sets,
AdaptationSet* new_adaptation_set) {
if (!language.empty() && language == mpd_options_.mpd_params.default_language)
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
if (media_info.has_video_info()) {
// Because 'language' is ignored for videos, |adaptation_sets| must have
// all the video AdaptationSets.
if (adaptation_sets.size() > 1) {
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
} else if (adaptation_sets.size() == 1) {
(*adaptation_sets.begin())->AddRole(AdaptationSet::kRoleMain);
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
}
if (media_info.video_info().has_playback_rate()) {
uint32_t trick_play_reference_id = 0;
if (!FindOriginalAdaptationSetForTrickPlay(media_info,
&trick_play_reference_id)) {
LOG(ERROR) << "Failed to find main adaptation set for trick play.";
return false;
}
DCHECK_NE(new_adaptation_set->id(), trick_play_reference_id);
new_adaptation_set->AddTrickPlayReferenceId(trick_play_reference_id);
}
} else if (media_info.has_text_info()) {
// IOP requires all AdaptationSets to have (sub)segmentAlignment set to
// true, so carelessly set it to true.
// In practice it doesn't really make sense to adapt between text tracks.
new_adaptation_set->ForceSetSegmentAlignment(true);
}
return true;
}
bool Period::FindOriginalAdaptationSetForTrickPlay(
const MediaInfo& media_info,
uint32_t* main_adaptation_set_id) {
MediaInfo media_info_no_trickplay = media_info;
media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
std::string key = GetAdaptationSetKey(media_info_no_trickplay);
const std::list<AdaptationSet*>& adaptation_sets =
adaptation_set_list_map_[key];
for (AdaptationSet* adaptation_set : adaptation_sets) {
if (protected_adaptation_set_map_.Match(*adaptation_set, media_info)) {
*main_adaptation_set_id = adaptation_set->id();
return true;
}
}
return false;
}
void Period::ProtectedAdaptationSetMap::Register(
const AdaptationSet& adaptation_set,
const MediaInfo& media_info) {
DCHECK(!ContainsKey(protected_content_map_, adaptation_set.id()));
protected_content_map_[adaptation_set.id()] = media_info.protected_content();
}
bool Period::ProtectedAdaptationSetMap::Match(
const AdaptationSet& adaptation_set,
const MediaInfo& media_info) {
const auto protected_content_it =
protected_content_map_.find(adaptation_set.id());
// If the AdaptationSet ID is not registered in the map, then it is clear
// content.
if (protected_content_it == protected_content_map_.end())
return !media_info.has_protected_content();
if (!media_info.has_protected_content())
return false;
return ProtectedContentEq(protected_content_it->second,
media_info.protected_content());
}
bool Period::ProtectedAdaptationSetMap::Switchable(
const AdaptationSet& adaptation_set_a,
const AdaptationSet& adaptation_set_b) {
const auto protected_content_it_a =
protected_content_map_.find(adaptation_set_a.id());
const auto protected_content_it_b =
protected_content_map_.find(adaptation_set_b.id());
if (protected_content_it_a == protected_content_map_.end())
return protected_content_it_b == protected_content_map_.end();
if (protected_content_it_b == protected_content_map_.end())
return false;
// Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the
// same UUIDs then those are switchable.
return GetUUIDs(protected_content_it_a->second) ==
GetUUIDs(protected_content_it_b->second);
}
} // namespace shaka

135
packager/mpd/base/period.h Normal file
View File

@ -0,0 +1,135 @@
// Copyright 2017 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
//
/// All the methods that are virtual are virtual for mocking.
#ifndef PACKAGER_MPD_BASE_PERIOD_H_
#define PACKAGER_MPD_BASE_PERIOD_H_
#include <list>
#include <map>
#include "packager/base/atomic_sequence_num.h"
#include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
namespace shaka {
struct MpdOptions;
namespace xml {
class XmlNode;
} // namespace xml
/// Period class maps to <Period> element and provides methods to add
/// AdaptationSets.
class Period {
public:
/// Check the existing AdaptationSets, if there is one matching the provided
/// @a media_info, return it; otherwise a new AdaptationSet is created and
/// returned.
/// @param media_info contains media information, which is used to match
/// AdaptationSets.
/// @param content_protection_in_adaptation_set determines if the
/// ContentProtection is placed in AdaptationSet or Representation
/// element. This affects how MediaInfo in AdaptationSets are matched.
/// @return the AdaptationSet matching @a media_info if found; otherwise
/// return a new AdaptationSet.
virtual AdaptationSet* GetOrCreateAdaptationSet(
const MediaInfo& media_info,
bool content_protection_in_adaptation_set);
/// Generates <Period> xml element with its child AdaptationSet elements.
/// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a
/// NULL scoped_xml_ptr.
xml::scoped_xml_ptr<xmlNode> GetXml();
protected:
/// @param mpd_options is the options for this MPD.
/// @param adaptation_set_counter is a counter for assigning ID numbers to
/// AdaptationSet. It can not be NULL.
/// @param representation_counter is a counter for assigning ID numbers to
/// Representation. It can not be NULL.
Period(const MpdOptions& mpd_options,
base::AtomicSequenceNumber* adaptation_set_counter,
base::AtomicSequenceNumber* representation_counter);
private:
Period(const Period&) = delete;
Period& operator=(const Period&) = delete;
friend class MpdBuilder;
friend class PeriodTest;
// Gets the earliest, normalized segment timestamp. Returns true on success,
// false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
// Calls AdaptationSet constructor. For mock injection.
virtual std::unique_ptr<AdaptationSet> NewAdaptationSet(
uint32_t adaptation_set_id,
const std::string& lang,
const MpdOptions& options,
base::AtomicSequenceNumber* representation_counter);
// Helper function to set new AdaptationSet attributes.
bool SetNewAdaptationSetAttributes(
const std::string& language,
const MediaInfo& media_info,
const std::list<AdaptationSet*>& adaptation_sets,
AdaptationSet* new_adaptation_set);
// Gets the original AdaptationSet which the trick play video belongs
// to and returns the id of the original adapatation set.
// It is assumed that the corresponding AdaptationSet has been created before
// the trick play AdaptationSet.
// Returns true if main_adaptation_id is found, otherwise false;
bool FindOriginalAdaptationSetForTrickPlay(
const MediaInfo& media_info,
uint32_t* original_adaptation_set_id);
const MpdOptions& mpd_options_;
base::AtomicSequenceNumber* const adaptation_set_counter_;
base::AtomicSequenceNumber* const representation_counter_;
// The list of AdaptationSets in this Period.
std::list<std::unique_ptr<AdaptationSet>> adaptation_sets_;
// AdaptationSets grouped by a specific adaptation set grouping key.
// AdaptationSets with the same key contain identical parameters except
// ContentProtection parameters. A single AdaptationSet would be created
// if they contain identical ContentProtection elements. This map is only
// useful when ContentProtection element is placed in AdaptationSet.
std::map<std::string, std::list<AdaptationSet*>> adaptation_set_list_map_;
// Tracks ProtectedContent in AdaptationSet.
class ProtectedAdaptationSetMap {
public:
ProtectedAdaptationSetMap() = default;
// Register the |adaptation_set| with associated |media_info| in the map.
void Register(const AdaptationSet& adaptation_set,
const MediaInfo& media_info);
// Check if the protected content associated with |adaptation_set| matches
// with the one in |media_info|.
bool Match(const AdaptationSet& adaptation_set,
const MediaInfo& media_info);
// Check if the two adaptation sets are switchable.
bool Switchable(const AdaptationSet& adaptation_set_a,
const AdaptationSet& adaptation_set_b);
private:
ProtectedAdaptationSetMap(const ProtectedAdaptationSetMap&) = delete;
ProtectedAdaptationSetMap& operator=(const ProtectedAdaptationSetMap&) =
delete;
// AdaptationSet id => ProtectedContent map.
std::map<uint32_t, MediaInfo::ProtectedContent> protected_content_map_;
};
ProtectedAdaptationSetMap protected_adaptation_set_map_;
};
} // namespace shaka
#endif // PACKAGER_MPD_BASE_PERIOD_H_

View File

@ -0,0 +1,773 @@
// Copyright 2017 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/period.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "packager/mpd/base/mock_mpd_builder.h"
#include "packager/mpd/base/mpd_options.h"
#include "packager/mpd/test/mpd_builder_test_helper.h"
#include "packager/mpd/test/xml_compare.h"
using ::testing::_;
using ::testing::ByMove;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::UnorderedElementsAre;
namespace shaka {
namespace {
const uint32_t kDefaultAdaptationSetId = 0u;
const uint32_t kTrickPlayAdaptationSetId = 1u;
const bool kContentProtectionInAdaptationSet = true;
bool ElementEqual(const Element& lhs, const 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 ContentProtectionElement& lhs,
const 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);
}
/// A Period class that is capable of injecting mocked AdaptationSet.
class TestablePeriod : public Period {
public:
TestablePeriod(const MpdOptions& mpd_options)
: Period(mpd_options, &sequence_number_, &sequence_number_) {}
MOCK_METHOD4(NewAdaptationSet,
std::unique_ptr<AdaptationSet>(
uint32_t adaptation_set_id,
const std::string& lang,
const MpdOptions& options,
base::AtomicSequenceNumber* representation_counter));
private:
// Only for constructing the super class. Not used for testing.
base::AtomicSequenceNumber sequence_number_;
};
} // namespace
class PeriodTest : public ::testing::Test {
public:
PeriodTest()
: testable_period_(mpd_options_),
default_adaptation_set_(
new StrictMock<MockAdaptationSet>(kDefaultAdaptationSetId)),
default_adaptation_set_ptr_(default_adaptation_set_.get()) {}
protected:
MpdOptions mpd_options_;
TestablePeriod testable_period_;
// Default mock that can be used for the tests.
std::unique_ptr<StrictMock<MockAdaptationSet>> default_adaptation_set_;
StrictMock<MockAdaptationSet>* default_adaptation_set_ptr_;
};
TEST_F(PeriodTest, GetXml) {
const char kVideoMediaInfo[] =
"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";
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(default_adaptation_set_))));
ASSERT_EQ(default_adaptation_set_ptr_,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVideoMediaInfo),
kContentProtectionInAdaptationSet));
const char kExpectedXml[] =
"<Period id=\"0\">"
// ContentType and Representation elements are populated after
// Representation::Init() is called.
" <AdaptationSet id=\"0\" contentType=\"\"/>"
"</Period>";
EXPECT_THAT(testable_period_.GetXml().get(), XmlNodeEqual(kExpectedXml));
}
TEST_F(PeriodTest, DynamicMpdGetXml) {
const char kVideoMediaInfo[] =
"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";
mpd_options_.mpd_type = MpdType::kDynamic;
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(default_adaptation_set_))));
ASSERT_EQ(default_adaptation_set_ptr_,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVideoMediaInfo),
kContentProtectionInAdaptationSet));
const char kExpectedXml[] =
"<Period id=\"0\" start=\"PT0S\">"
// ContentType and Representation elements are populated after
// Representation::Init() is called.
" <AdaptationSet id=\"0\" contentType=\"\"/>"
"</Period>";
EXPECT_THAT(testable_period_.GetXml().get(), XmlNodeEqual(kExpectedXml));
}
// Verify ForceSetSegmentAlignment is called.
TEST_F(PeriodTest, Text) {
const char kTextMediaInfo[] =
"text_info {\n"
" format: 'ttml'\n"
" language: 'en'\n"
"}\n"
"container_type: CONTAINER_TEXT\n";
EXPECT_CALL(testable_period_, NewAdaptationSet(_, Eq("en"), _, _))
.WillOnce(Return(ByMove(std::move(default_adaptation_set_))));
EXPECT_CALL(*default_adaptation_set_ptr_, ForceSetSegmentAlignment(true));
ASSERT_EQ(default_adaptation_set_ptr_,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kTextMediaInfo),
kContentProtectionInAdaptationSet));
}
// Verify AddTrickPlayReferenceId is called.
TEST_F(PeriodTest, TrickPlayWithMatchingAdaptationSet) {
const char kVideoMediaInfo[] =
"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 char kTrickPlayMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 10\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
" playback_rate: 10\n"
"}\n"
"container_type: 1\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> trick_play_adaptation_set(
new StrictMock<MockAdaptationSet>(kTrickPlayAdaptationSetId));
auto* trick_play_adaptation_set_ptr = trick_play_adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(default_adaptation_set_))))
.WillOnce(Return(ByMove(std::move(trick_play_adaptation_set))));
EXPECT_CALL(*trick_play_adaptation_set_ptr,
AddTrickPlayReferenceId(Eq(kDefaultAdaptationSetId)));
ASSERT_EQ(default_adaptation_set_ptr_,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVideoMediaInfo),
kContentProtectionInAdaptationSet));
ASSERT_EQ(trick_play_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kTrickPlayMediaInfo),
kContentProtectionInAdaptationSet));
}
// Verify no AdaptationSet is returned on trickplay media info.
TEST_F(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
const char kVideoMediaInfo[] =
"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 char kVp9TrickPlayMediaInfo[] =
"video_info {\n"
" codec: 'vp9'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 10\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
" playback_rate: 10\n"
"}\n"
"container_type: 1\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> trick_play_adaptation_set(
new StrictMock<MockAdaptationSet>(kTrickPlayAdaptationSetId));
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(default_adaptation_set_))))
.WillOnce(Return(ByMove(std::move(trick_play_adaptation_set))));
ASSERT_EQ(default_adaptation_set_ptr_,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVideoMediaInfo),
kContentProtectionInAdaptationSet));
// A nullptr is returned if it is not able to find matching AdaptationSet.
ASSERT_FALSE(testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVp9TrickPlayMediaInfo),
kContentProtectionInAdaptationSet));
}
// Verify with different MediaInfo::ProtectedContent, two AdaptationSets should
// be created. AdaptationSets with different DRM won't be switchable.
TEST_F(PeriodTest, DifferentProtectedContent) {
// 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;
std::unique_ptr<StrictMock<MockAdaptationSet>> sd_adaptation_set(
new StrictMock<MockAdaptationSet>(kSdAdaptationSetId));
auto* sd_adaptation_set_ptr = sd_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> hd_adaptation_set(
new StrictMock<MockAdaptationSet>(kHdAdaptationSetId));
auto* hd_adaptation_set_ptr = hd_adaptation_set.get();
InSequence in_sequence;
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(sd_adaptation_set))));
EXPECT_CALL(
*sd_adaptation_set_ptr,
AddContentProtectionElement(ContentProtectionElementEq(mp4_protection)));
EXPECT_CALL(
*sd_adaptation_set_ptr,
AddContentProtectionElement(ContentProtectionElementEq(sd_my_drm)));
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(hd_adaptation_set))));
// Add main Role here for both.
EXPECT_CALL(*sd_adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
EXPECT_CALL(*hd_adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
// Called twice for the same reason as above.
EXPECT_CALL(*hd_adaptation_set_ptr, AddContentProtectionElement(_)).Times(2);
ASSERT_EQ(sd_adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kSdProtectedContent),
kContentProtectionInAdaptationSet));
ASSERT_EQ(hd_adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kHdProtectedContent),
kContentProtectionInAdaptationSet));
EXPECT_THAT(sd_adaptation_set_ptr->adaptation_set_switching_ids(),
ElementsAre());
EXPECT_THAT(hd_adaptation_set_ptr->adaptation_set_switching_ids(),
ElementsAre());
}
// Verify with the same MediaInfo::ProtectedContent, only one AdaptationSets
// should be created.
TEST_F(PeriodTest, SameProtectedContent) {
// 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);
InSequence in_sequence;
// Only called once.
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(default_adaptation_set_))));
EXPECT_CALL(
*default_adaptation_set_ptr_,
AddContentProtectionElement(ContentProtectionElementEq(mp4_protection)));
EXPECT_CALL(*default_adaptation_set_ptr_,
AddContentProtectionElement(ContentProtectionElementEq(my_drm)));
ASSERT_EQ(default_adaptation_set_ptr_,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kSdProtectedContent),
kContentProtectionInAdaptationSet));
ASSERT_EQ(default_adaptation_set_ptr_,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kHdProtectedContent),
kContentProtectionInAdaptationSet));
// No adaptation set switching if there is only one AdaptationSet.
EXPECT_THAT(default_adaptation_set_ptr_->adaptation_set_switching_ids(),
ElementsAre());
}
// Default Key IDs are different but if the content protection UUIDs match, then
// the AdaptationSet they belong to should be switchable.
// 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. SD AdaptationSet and HD AdaptationSet
// should be switchable.
// 3. Add a 4k protected content. This should also make a new AdaptationSet.
// It should be switchable with SD/HD AdaptationSet.
TEST_F(PeriodTest, SetAdaptationSetSwitching) {
// 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;
std::unique_ptr<StrictMock<MockAdaptationSet>> sd_adaptation_set(
new StrictMock<MockAdaptationSet>(kSdAdaptationSetId));
auto* sd_adaptation_set_ptr = sd_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> hd_adaptation_set(
new StrictMock<MockAdaptationSet>(kHdAdaptationSetId));
auto* hd_adaptation_set_ptr = hd_adaptation_set.get();
InSequence in_sequence;
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(sd_adaptation_set))));
EXPECT_CALL(*sd_adaptation_set_ptr, AddContentProtectionElement(_)).Times(2);
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(hd_adaptation_set))));
// Add main Role here for both.
EXPECT_CALL(*sd_adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
EXPECT_CALL(*hd_adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
EXPECT_CALL(*hd_adaptation_set_ptr, AddContentProtectionElement(_)).Times(2);
ASSERT_EQ(sd_adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kSdProtectedContent),
kContentProtectionInAdaptationSet));
ASSERT_EQ(hd_adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kHdProtectedContent),
kContentProtectionInAdaptationSet));
EXPECT_THAT(sd_adaptation_set_ptr->adaptation_set_switching_ids(),
ElementsAre(kHdAdaptationSetId));
EXPECT_THAT(hd_adaptation_set_ptr->adaptation_set_switching_ids(),
ElementsAre(kSdAdaptationSetId));
// Add another content that has the same protected content and make sure that
// adaptation set switching is set correctly.
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;
std::unique_ptr<StrictMock<MockAdaptationSet>> fourk_adaptation_set(
new StrictMock<MockAdaptationSet>(k4kAdaptationSetId));
auto* fourk_adaptation_set_ptr = fourk_adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(fourk_adaptation_set))));
EXPECT_CALL(*fourk_adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
EXPECT_CALL(*fourk_adaptation_set_ptr, AddContentProtectionElement(_))
.Times(2);
ASSERT_EQ(fourk_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(k4kProtectedContent),
kContentProtectionInAdaptationSet));
EXPECT_THAT(sd_adaptation_set_ptr->adaptation_set_switching_ids(),
UnorderedElementsAre(kHdAdaptationSetId, k4kAdaptationSetId));
EXPECT_THAT(hd_adaptation_set_ptr->adaptation_set_switching_ids(),
UnorderedElementsAre(kSdAdaptationSetId, k4kAdaptationSetId));
EXPECT_THAT(fourk_adaptation_set_ptr->adaptation_set_switching_ids(),
ElementsAre(kSdAdaptationSetId, kHdAdaptationSetId));
}
// Even if the UUIDs match, video and audio AdaptationSets should not be
// switchable.
TEST_F(PeriodTest, DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
// 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 kVideoAdaptationSetId = 6u;
const uint32_t kAudioAdaptationSetId = 7u;
std::unique_ptr<StrictMock<MockAdaptationSet>> video_adaptation_set(
new StrictMock<MockAdaptationSet>(kVideoAdaptationSetId));
auto* video_adaptation_set_ptr = video_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> audio_adaptation_set(
new StrictMock<MockAdaptationSet>(kAudioAdaptationSetId));
auto* audio_adaptation_set_ptr = audio_adaptation_set.get();
InSequence in_sequence;
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(video_adaptation_set))));
EXPECT_CALL(*video_adaptation_set_ptr, AddContentProtectionElement(_))
.Times(2);
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(audio_adaptation_set))));
EXPECT_CALL(*audio_adaptation_set_ptr, AddContentProtectionElement(_))
.Times(2);
ASSERT_EQ(video_adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVideoContent),
kContentProtectionInAdaptationSet));
ASSERT_EQ(audio_adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAudioContent),
kContentProtectionInAdaptationSet));
EXPECT_THAT(video_adaptation_set_ptr->adaptation_set_switching_ids(),
ElementsAre());
EXPECT_THAT(audio_adaptation_set_ptr->adaptation_set_switching_ids(),
ElementsAre());
}
// Don't put different audio languages or codecs in the same AdaptationSet.
TEST_F(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
const char kAacEnglishAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kAacGermanAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kVorbisGermanAudioContent1[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
const char kVorbisGermanAudioContent2[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_eng_adaptation_set(
new StrictMock<MockAdaptationSet>(1));
auto* aac_eng_adaptation_set_ptr = aac_eng_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_ger_adaptation_set(
new StrictMock<MockAdaptationSet>(2));
auto* aac_ger_adaptation_set_ptr = aac_ger_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> vorbis_german_adaptation_set(
new StrictMock<MockAdaptationSet>(3));
auto* vorbis_german_adaptation_set_ptr = vorbis_german_adaptation_set.get();
// We expect three AdaptationSets.
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(aac_eng_adaptation_set))))
.WillOnce(Return(ByMove(std::move(aac_ger_adaptation_set))))
.WillOnce(Return(ByMove(std::move(vorbis_german_adaptation_set))));
ASSERT_EQ(aac_eng_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAacEnglishAudioContent),
kContentProtectionInAdaptationSet));
ASSERT_EQ(aac_ger_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAacGermanAudioContent),
kContentProtectionInAdaptationSet));
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVorbisGermanAudioContent1),
kContentProtectionInAdaptationSet));
// The same AdaptationSet is returned.
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kVorbisGermanAudioContent2),
kContentProtectionInAdaptationSet));
}
} // namespace shaka

View File

@ -7,6 +7,7 @@
#include "packager/mpd/base/representation.h"
#include "packager/base/logging.h"
#include "packager/mpd/base/mpd_options.h"
#include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/xml/xml_node.h"

View File

@ -6,9 +6,11 @@
//
/// All the methods that are virtual are virtual for mocking.
#ifndef PACKAGER_MPD_BASE_REPRESENTATION_H_
#define PACKAGER_MPD_BASE_REPRESENTATION_H_
#include "packager/mpd/base/bandwidth_estimator.h"
#include "packager/mpd/base/media_info.pb.h"
#include "packager/mpd/base/mpd_options.h"
#include "packager/mpd/base/segment_info.h"
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
@ -20,6 +22,7 @@
namespace shaka {
struct ContentProtectionElement;
struct MpdOptions;
namespace xml {
class XmlNode;
@ -198,3 +201,5 @@ class Representation {
};
} // namespace shaka
#endif // PACKAGER_MPD_BASE_REPRESENTATION_H_

View File

@ -11,6 +11,7 @@
#include <inttypes.h>
#include "packager/base/strings/stringprintf.h"
#include "packager/mpd/base/mpd_options.h"
#include "packager/mpd/test/mpd_builder_test_helper.h"
#include "packager/mpd/test/xml_compare.h"

View File

@ -12,8 +12,13 @@
#include "packager/mpd/base/mpd_builder.h"
#include "packager/mpd/base/mpd_notifier_util.h"
#include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/period.h"
#include "packager/mpd/base/representation.h"
namespace {
const bool kContentProtectionInAdaptationSet = true;
} // namespace
namespace shaka {
SimpleMpdNotifier::SimpleMpdNotifier(const MpdOptions& mpd_options)
@ -40,20 +45,16 @@ bool SimpleMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
return false;
base::AutoLock auto_lock(lock_);
if (!period_)
period_ = mpd_builder_->AddPeriod();
AdaptationSet* adaptation_set = period_->GetOrCreateAdaptationSet(
media_info, !kContentProtectionInAdaptationSet);
DCHECK(adaptation_set);
// TODO(kqyang): Consider adding a new method MpdBuilder::AddRepresentation.
// Most of the codes here can be moved inside.
std::string key = GetAdaptationSetKey(media_info);
std::string lang = GetLanguage(media_info);
AdaptationSet** adaptation_set = &adaptation_set_map_[key];
if (*adaptation_set == NULL)
*adaptation_set = mpd_builder_->AddAdaptationSet(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);
adaptation_set->AddRepresentation(adjusted_media_info);
if (representation == NULL)
return false;

View File

@ -21,6 +21,7 @@ namespace shaka {
class AdaptationSet;
class MpdBuilder;
class Period;
class Representation;
class SimpleMpdNotifierTest;
@ -69,6 +70,7 @@ class SimpleMpdNotifier : public MpdNotifier {
// MPD output path.
std::string output_path_;
std::unique_ptr<MpdBuilder> mpd_builder_;
Period* period_ = nullptr;
base::Lock lock_;
typedef std::map<std::string, AdaptationSet*> AdaptationSetMap;

View File

@ -4,6 +4,8 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include <gmock/gmock.h>
#include <google/protobuf/util/message_differencer.h>
#include <gtest/gtest.h>
#include "packager/base/files/file_path.h"
@ -17,33 +19,48 @@
namespace shaka {
using ::testing::_;
using ::testing::Eq;
using ::testing::Return;
using ::testing::StrEq;
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 bool kContentProtectionInAdaptationSet = true;
MATCHER_P(EqualsProto, message, "") {
return ::google::protobuf::util::MessageDifferencer::Equals(arg, message);
}
} // namespace
class SimpleMpdNotifierTest : public ::testing::Test {
protected:
SimpleMpdNotifierTest()
: default_mock_adaptation_set_(
: default_mock_period_(new MockPeriod),
default_mock_adaptation_set_(
new MockAdaptationSet(kDefaultAdaptationSetId)) {}
void SetUp() override {
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
empty_mpd_option_.mpd_params.mpd_output = temp_file_path_.AsUTF8Unsafe();
// Three valid media info. The actual data does not matter.
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";
valid_media_info1_ = ConvertToMediaInfo(kValidMediaInfo);
valid_media_info2_ = valid_media_info1_;
valid_media_info2_.mutable_video_info()->set_width(960);
valid_media_info3_ = valid_media_info1_;
valid_media_info3_.mutable_video_info()->set_width(480);
}
void TearDown() override {
@ -55,14 +72,21 @@ class SimpleMpdNotifierTest : public ::testing::Test {
notifier->SetMpdBuilderForTesting(std::move(mpd_builder));
}
protected:
// Empty mpd options except with output path specified, so that
// WriteMpdToFile() doesn't crash.
MpdOptions empty_mpd_option_;
const std::vector<std::string> empty_base_urls_;
// Default AdaptationSet mock.
// Default mocks that can be used for the tests.
std::unique_ptr<MockPeriod> default_mock_period_;
std::unique_ptr<MockAdaptationSet> default_mock_adaptation_set_;
// Three valid media info. The actual content does not matter.
MediaInfo valid_media_info1_;
MediaInfo valid_media_info2_;
MediaInfo valid_media_info3_;
private:
base::FilePath temp_file_path_;
};
@ -76,9 +100,14 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewContainer) {
std::unique_ptr<MockRepresentation> mock_representation(
new MockRepresentation(kRepresentationId));
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_,
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_),
Eq(!kContentProtectionInAdaptationSet)))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
EXPECT_CALL(*default_mock_adaptation_set_,
AddRepresentation(EqualsProto(valid_media_info1_)))
.WillOnce(Return(mock_representation.get()));
// This is for the Flush() below but adding expectation here because the next
@ -87,8 +116,8 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewContainer) {
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&unused_container_id));
EXPECT_TRUE(
notifier.NotifyNewContainer(valid_media_info1_, &unused_container_id));
EXPECT_TRUE(notifier.Flush());
}
@ -100,15 +129,16 @@ TEST_F(SimpleMpdNotifierTest, NotifySampleDuration) {
std::unique_ptr<MockRepresentation> mock_representation(
new MockRepresentation(kRepresentationId));
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(mock_representation.get()));
uint32_t container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
EXPECT_EQ(kRepresentationId, container_id);
const uint32_t kSampleDuration = 100;
@ -125,8 +155,7 @@ TEST_F(SimpleMpdNotifierTest, NotifySampleDuration) {
TEST_F(SimpleMpdNotifierTest, NotifyNewContainerAndSampleDurationNoMock) {
SimpleMpdNotifier notifier(empty_mpd_option_);
uint32_t container_id;
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
const uint32_t kAnySampleDuration = 1000;
EXPECT_TRUE(notifier.NotifySampleDuration(container_id, kAnySampleDuration));
EXPECT_TRUE(notifier.Flush());
@ -140,15 +169,16 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewSegment) {
std::unique_ptr<MockRepresentation> mock_representation(
new MockRepresentation(kRepresentationId));
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(mock_representation.get()));
uint32_t container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
EXPECT_EQ(kRepresentationId, container_id);
const uint64_t kStartTime = 0u;
@ -169,15 +199,16 @@ TEST_F(SimpleMpdNotifierTest, AddContentProtectionElement) {
std::unique_ptr<MockRepresentation> mock_representation(
new MockRepresentation(kRepresentationId));
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(mock_representation.get()));
uint32_t container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
&container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
EXPECT_EQ(kRepresentationId, container_id);
ContentProtectionElement element;
@ -186,40 +217,22 @@ TEST_F(SimpleMpdNotifierTest, AddContentProtectionElement) {
}
TEST_F(SimpleMpdNotifierTest, UpdateEncryption) {
const char kProtectedContent[] =
"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: 'psshsomethingelse'\n"
" }\n"
" default_key_id: '_default_key_id_'\n"
"}\n"
"container_type: 1\n";
SimpleMpdNotifier notifier(empty_mpd_option_);
const uint32_t kRepresentationId = 447834u;
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
std::unique_ptr<MockRepresentation> mock_representation(
new MockRepresentation(kRepresentationId));
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
.WillOnce(Return(default_mock_adaptation_set_.get()));
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
.WillOnce(Return(mock_representation.get()));
uint32_t container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kProtectedContent),
&container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
::testing::Mock::VerifyAndClearExpectations(
default_mock_adaptation_set_.get());
@ -239,66 +252,13 @@ TEST_F(SimpleMpdNotifierTest, UpdateEncryption) {
container_id, "myuuid", std::vector<uint8_t>(), kBogusNewPsshVector));
}
// Don't put different audio languages or codecs in the same AdaptationSet.
TEST_F(SimpleMpdNotifierTest, SplitAdaptationSetsByLanguageAndCodec) {
// MP4, English
const char kAudioContent1[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
// MP4, German
const char kAudioContent2[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
// WebM, German
const char kAudioContent3[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
// WebM, German again
const char kAudioContent4[] =
"audio_info {\n"
" codec: 'vorbis'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n";
// Test multiple media info with some belongs to the same AdaptationSets.
TEST_F(SimpleMpdNotifierTest, MultipleMediaInfo) {
SimpleMpdNotifier notifier(empty_mpd_option_);
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
std::unique_ptr<MockAdaptationSet> adaptation_set1(new MockAdaptationSet(1));
std::unique_ptr<MockAdaptationSet> adaptation_set2(new MockAdaptationSet(2));
std::unique_ptr<MockAdaptationSet> adaptation_set3(new MockAdaptationSet(3));
std::unique_ptr<MockRepresentation> representation1(
new MockRepresentation(1));
@ -306,35 +266,40 @@ TEST_F(SimpleMpdNotifierTest, SplitAdaptationSetsByLanguageAndCodec) {
new MockRepresentation(2));
std::unique_ptr<MockRepresentation> representation3(
new MockRepresentation(3));
std::unique_ptr<MockRepresentation> representation4(
new MockRepresentation(4));
// We expect three AdaptationSets.
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
.WillOnce(Return(adaptation_set1.get()))
.WillOnce(Return(adaptation_set2.get()))
.WillOnce(Return(adaptation_set3.get()));
// The first AdaptationSet should have Eng MP4, one Representation.
EXPECT_CALL(*adaptation_set1, AddRepresentation(_))
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
.WillOnce(Return(default_mock_period_.get()));
EXPECT_CALL(*default_mock_period_,
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
.WillOnce(Return(adaptation_set1.get()));
EXPECT_CALL(*adaptation_set1,
AddRepresentation(EqualsProto(valid_media_info1_)))
.WillOnce(Return(representation1.get()));
// The second AdaptationSet should have Ger MP4, one Representation.
EXPECT_CALL(*adaptation_set2, AddRepresentation(_))
// Return the same adaptation set for |valid_media_info2_| and
// |valid_media_info3_|. This results in AddRepresentation to be called twice
// on |adaptation_set2|.
EXPECT_CALL(*default_mock_period_,
GetOrCreateAdaptationSet(EqualsProto(valid_media_info2_), _))
.WillOnce(Return(adaptation_set2.get()));
EXPECT_CALL(*adaptation_set2,
AddRepresentation(EqualsProto(valid_media_info2_)))
.WillOnce(Return(representation2.get()));
// The third AdaptationSet should have Ger WebM, two Representations.
EXPECT_CALL(*adaptation_set3, AddRepresentation(_))
.WillOnce(Return(representation3.get()))
.WillOnce(Return(representation4.get()));
EXPECT_CALL(*default_mock_period_,
GetOrCreateAdaptationSet(EqualsProto(valid_media_info3_), _))
.WillOnce(Return(adaptation_set2.get()));
EXPECT_CALL(*adaptation_set2,
AddRepresentation(EqualsProto(valid_media_info3_)))
.WillOnce(Return(representation3.get()));
uint32_t unused_container_id;
SetMpdBuilder(&notifier, std::move(mock_mpd_builder));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent1), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent2), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent3), &unused_container_id));
EXPECT_TRUE(notifier.NotifyNewContainer(
ConvertToMediaInfo(kAudioContent4), &unused_container_id));
EXPECT_TRUE(
notifier.NotifyNewContainer(valid_media_info1_, &unused_container_id));
EXPECT_TRUE(
notifier.NotifyNewContainer(valid_media_info2_, &unused_container_id));
EXPECT_TRUE(
notifier.NotifyNewContainer(valid_media_info3_, &unused_container_id));
}
} // namespace shaka

View File

@ -43,6 +43,8 @@
'base/mpd_options.h',
'base/mpd_utils.cc',
'base/mpd_utils.h',
'base/period.cc',
'base/period.h',
'base/representation.cc',
'base/representation.h',
'base/segment_info.h',
@ -88,6 +90,7 @@
'base/bandwidth_estimator_unittest.cc',
'base/dash_iop_mpd_notifier_unittest.cc',
'base/mpd_builder_unittest.cc',
'base/period_unittest.cc',
'base/representation_unittest.cc',
'base/simple_mpd_notifier_unittest.cc',
'base/xml/xml_node_unittest.cc',

View File

@ -2,7 +2,7 @@
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT10.5S">
<Period id="0">
<AdaptationSet id="0" width="720" height="480" frameRate="10/1" contentType="video" par="3:2">
<Representation id="1" bandwidth="7620" codecs="avc1.010101" mimeType="video/mp4" sar="1:1">
<Representation id="0" bandwidth="7620" codecs="avc1.010101" mimeType="video/mp4" sar="1:1">
<BaseURL>test_output_file_name1.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="1000">
<Initialization range="0-120"/>
@ -10,7 +10,7 @@
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio">
<Representation id="0" bandwidth="400" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<Representation id="1" bandwidth="400" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>test_output_file_name_audio1.mp4</BaseURL>
<SegmentBase timescale="50"/>