Fix AdaptationSet switching signaling
Current mechanism of using AdaptationSet @group is not spec compliant. The spec compliant mechanism is to use supplemental descriptor with schemeIdUri: urn:mpeg:dash:adaptation-set-switching:2016 as specified in ISO/IEC 23009-1:2014/Amd.4:2016. Fixes #156 Change-Id: I4d97648547a23315ba9c09dcadb14e18c99a45fc
This commit is contained in:
parent
7a4a40acb8
commit
af0904e428
|
@ -404,6 +404,19 @@ class PackagerAppTest(unittest.TestCase):
|
|||
self._AssertStreamInfo(self.output[0], 'is_encrypted: true')
|
||||
self._AssertStreamInfo(self.output[1], 'is_encrypted: true')
|
||||
|
||||
@unittest.skipUnless(test_env.has_aes_flags, 'Requires AES credentials.')
|
||||
def testWidevineEncryptionWithAesAndDashIfIopWithMultFiles(self):
|
||||
flags = self._GetFlags(widevine_encryption=True, dash_if_iop=True)
|
||||
flags += ['--aes_signing_key=' + test_env.options.aes_signing_key,
|
||||
'--aes_signing_iv=' + test_env.options.aes_signing_iv]
|
||||
self.packager.Package(
|
||||
self._GetStreams(['audio', 'video'],
|
||||
test_files=['bear-1280x720.mp4', 'bear-640x360.mp4',
|
||||
'bear-320x180.mp4']), flags)
|
||||
with open(self.mpd_output, 'rb') as f:
|
||||
print f.read()
|
||||
# TODO(kqyang): Add some validations.
|
||||
|
||||
@unittest.skipUnless(test_env.has_aes_flags, 'Requires AES credentials.')
|
||||
def testKeyRotationWithAes(self):
|
||||
flags = self._GetFlags(widevine_encryption=True, key_rotation=True)
|
||||
|
|
|
@ -15,8 +15,6 @@ namespace shaka {
|
|||
|
||||
namespace {
|
||||
|
||||
const int kStartingGroupId = 1;
|
||||
|
||||
// The easiest way to check whether two protobufs are equal, is to compare the
|
||||
// serialized version.
|
||||
bool ProtectedContentEq(
|
||||
|
@ -50,8 +48,7 @@ DashIopMpdNotifier::DashIopMpdNotifier(
|
|||
mpd_builder_(new MpdBuilder(dash_profile == kLiveProfile
|
||||
? MpdBuilder::kDynamic
|
||||
: MpdBuilder::kStatic,
|
||||
mpd_options)),
|
||||
next_group_id_(kStartingGroupId) {
|
||||
mpd_options)) {
|
||||
DCHECK(dash_profile == kLiveProfile || dash_profile == kOnDemandProfile);
|
||||
for (size_t i = 0; i < base_urls.size(); ++i)
|
||||
mpd_builder_->AddBaseUrl(base_urls[i]);
|
||||
|
@ -91,7 +88,7 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
|||
|
||||
representation_id_to_adaptation_set_[representation->id()] = adaptation_set;
|
||||
|
||||
SetGroupId(key, adaptation_set);
|
||||
SetAdaptationSetSwitching(key, adaptation_set);
|
||||
|
||||
*container_id = representation->id();
|
||||
DCHECK(!ContainsKey(representation_map_, representation->id()));
|
||||
|
@ -197,16 +194,18 @@ AdaptationSet* DashIopMpdNotifier::GetAdaptationSetForMediaInfo(
|
|||
}
|
||||
|
||||
// Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the
|
||||
// same UUIDs then those should be groupable.
|
||||
void DashIopMpdNotifier::SetGroupId(const std::string& key,
|
||||
AdaptationSet* adaptation_set) {
|
||||
if (adaptation_set->Group() >= 0) // @group already assigned.
|
||||
// same UUIDs then those are switchable.
|
||||
void DashIopMpdNotifier::SetAdaptationSetSwitching(
|
||||
const std::string& key,
|
||||
AdaptationSet* adaptation_set) {
|
||||
// This adaptation set is already visited.
|
||||
if (!adaptation_set->adaptation_set_switching_ids().empty())
|
||||
return;
|
||||
|
||||
ProtectedContentMap::const_iterator protected_content_it =
|
||||
protected_content_map_.find(adaptation_set->id());
|
||||
// Clear contents should be in one AdaptationSet, so no need to assign
|
||||
// @group.
|
||||
// Clear contents should be in one AdaptationSet and may not be switchable
|
||||
// with encrypted contents.
|
||||
if (protected_content_it == protected_content_map_.end()) {
|
||||
DVLOG(1) << "No content protection set for AdaptationSet@id="
|
||||
<< adaptation_set->id();
|
||||
|
@ -237,21 +236,15 @@ void DashIopMpdNotifier::SetGroupId(const std::string& key,
|
|||
protected_content_map_[loop_adaptation_set_id];
|
||||
if (static_cast<int>(adaptation_set_uuids.size()) !=
|
||||
loop_protected_content.content_protection_entry().size()) {
|
||||
// Different number of UUIDs, cannot be grouped.
|
||||
// Different number of UUIDs, may not be switchable.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (adaptation_set_uuids == GetUUIDs(loop_protected_content)) {
|
||||
AdaptationSet& uuid_match_adaptation_set = **adaptation_set_it;
|
||||
// They match. These AdaptationSets can be in the same group. Break out.
|
||||
if (uuid_match_adaptation_set.Group() >= 0) {
|
||||
adaptation_set->SetGroup(uuid_match_adaptation_set.Group());
|
||||
} else {
|
||||
const int group_id = next_group_id_++;
|
||||
uuid_match_adaptation_set.SetGroup(group_id);
|
||||
adaptation_set->SetGroup(group_id);
|
||||
}
|
||||
break;
|
||||
// They match. These AdaptationSets are switchable.
|
||||
uuid_match_adaptation_set.AddAdaptationSetSwitching(adaptation_set->id());
|
||||
adaptation_set->AddAdaptationSetSwitching(uuid_match_adaptation_set.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,9 +73,10 @@ class DashIopMpdNotifier : public MpdNotifier {
|
|||
AdaptationSet* GetAdaptationSetForMediaInfo(const std::string& key,
|
||||
const MediaInfo& media_info);
|
||||
|
||||
// Sets a group id for |adaptation_set| if applicable.
|
||||
// If a group ID is already assigned, then this returns immediately.
|
||||
void SetGroupId(const std::string& key, AdaptationSet* adaptation_set);
|
||||
// Sets adaptation set switching. If adaptation set switching is already
|
||||
// set, then this returns immediately.
|
||||
void SetAdaptationSetSwitching(const std::string& key,
|
||||
AdaptationSet* adaptation_set);
|
||||
|
||||
// Helper function to get a new AdaptationSet; registers the values
|
||||
// to the fields (maps) of the instance.
|
||||
|
@ -104,9 +105,6 @@ class DashIopMpdNotifier : public MpdNotifier {
|
|||
std::unique_ptr<MpdBuilder> mpd_builder_;
|
||||
base::Lock lock_;
|
||||
|
||||
// Next group ID to use for AdapationSets that can be grouped.
|
||||
int next_group_id_;
|
||||
|
||||
// Maps Representation ID to AdaptationSet. This is for updating the PSSH.
|
||||
std::map<uint32_t, AdaptationSet*> representation_id_to_adaptation_set_;
|
||||
};
|
||||
|
|
|
@ -18,9 +18,11 @@ namespace shaka {
|
|||
|
||||
using ::testing::_;
|
||||
using ::testing::Eq;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::InSequence;
|
||||
using ::testing::Return;
|
||||
using ::testing::StrEq;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -37,7 +39,6 @@ const char kValidMediaInfo[] =
|
|||
"container_type: 1\n";
|
||||
const uint32_t kDefaultAdaptationSetId = 0u;
|
||||
const uint32_t kDefaultRepresentationId = 1u;
|
||||
const int kDefaultGroupId = -1;
|
||||
|
||||
bool ElementEqual(const Element& lhs, const Element& rhs) {
|
||||
const bool all_equal_except_sublement_check =
|
||||
|
@ -102,8 +103,6 @@ class DashIopMpdNotifierTest
|
|||
void SetUp() override {
|
||||
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
|
||||
output_path_ = temp_file_path_.AsUTF8Unsafe();
|
||||
ON_CALL(*default_mock_adaptation_set_, Group())
|
||||
.WillByDefault(Return(kDefaultGroupId));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
|
@ -218,7 +217,7 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewTextContainer) {
|
|||
// Verify VOD NotifyNewContainer() operation works with different
|
||||
// MediaInfo::ProtectedContent.
|
||||
// Two AdaptationSets should be created.
|
||||
// Different DRM so they won't be grouped.
|
||||
// AdaptationSets with different DRM won't be switchable.
|
||||
TEST_P(DashIopMpdNotifierTest,
|
||||
NotifyNewContainersWithDifferentProtectedContent) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
|
@ -295,11 +294,6 @@ TEST_P(DashIopMpdNotifierTest,
|
|||
std::unique_ptr<MockAdaptationSet> hd_adaptation_set(
|
||||
new MockAdaptationSet(kHdAdaptationSetId));
|
||||
|
||||
ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId));
|
||||
ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId));
|
||||
EXPECT_CALL(*sd_adaptation_set, SetGroup(_)).Times(0);
|
||||
EXPECT_CALL(*hd_adaptation_set, SetGroup(_)).Times(0);
|
||||
|
||||
const uint32_t kSdRepresentation = 4u;
|
||||
const uint32_t kHdRepresentation = 5u;
|
||||
std::unique_ptr<MockRepresentation> sd_representation(
|
||||
|
@ -336,6 +330,9 @@ TEST_P(DashIopMpdNotifierTest,
|
|||
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
|
||||
|
@ -410,9 +407,6 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewContainersWithSameProtectedContent) {
|
|||
std::unique_ptr<MockRepresentation> hd_representation(
|
||||
new MockRepresentation(kHdRepresentation));
|
||||
|
||||
// No reason to set @group if there is only one AdaptationSet.
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, SetGroup(_)).Times(0);
|
||||
|
||||
InSequence in_sequence;
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||
|
@ -440,6 +434,10 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewContainersWithSameProtectedContent) {
|
|||
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.
|
||||
|
@ -466,17 +464,16 @@ TEST_P(DashIopMpdNotifierTest, AddContentProtection) {
|
|||
}
|
||||
|
||||
// Default Key IDs are different but if the content protection UUIDs match, then
|
||||
// they can be in the same group.
|
||||
// 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. Both SD and HD should have the same
|
||||
// group ID assigned.
|
||||
// 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.
|
||||
// The group ID should also match the SD and HD (but this takes a slightly
|
||||
// different path).
|
||||
TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
||||
// It should be switchable with SD/HD AdaptationSet.
|
||||
TEST_P(DashIopMpdNotifierTest, SetAdaptationSetSwitching) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(
|
||||
|
@ -530,9 +527,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
|||
std::unique_ptr<MockAdaptationSet> hd_adaptation_set(
|
||||
new MockAdaptationSet(kHdAdaptationSetId));
|
||||
|
||||
ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId));
|
||||
ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId));
|
||||
|
||||
const uint32_t kSdRepresentation = 4u;
|
||||
const uint32_t kHdRepresentation = 5u;
|
||||
std::unique_ptr<MockRepresentation> sd_representation(
|
||||
|
@ -553,11 +547,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
|||
EXPECT_CALL(*hd_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(hd_representation.get()));
|
||||
|
||||
// Both AdaptationSets' groups should be set to the same value.
|
||||
const int kExpectedGroupId = 1;
|
||||
EXPECT_CALL(*sd_adaptation_set, SetGroup(kExpectedGroupId));
|
||||
EXPECT_CALL(*hd_adaptation_set, SetGroup(kExpectedGroupId));
|
||||
|
||||
// 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;
|
||||
|
@ -567,12 +556,13 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
|||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
|
||||
|
||||
// Now that the group IDs are set Group() returns kExpectedGroupId.
|
||||
ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kExpectedGroupId));
|
||||
ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kExpectedGroupId));
|
||||
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
|
||||
// it gets added to the existing group.
|
||||
// adaptation set switching is set correctly.
|
||||
const char k4kProtectedContent[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
|
@ -596,8 +586,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
|||
const uint32_t k4kAdaptationSetId = 4000u;
|
||||
std::unique_ptr<MockAdaptationSet> fourk_adaptation_set(
|
||||
new MockAdaptationSet(k4kAdaptationSetId));
|
||||
ON_CALL(*fourk_adaptation_set, Group())
|
||||
.WillByDefault(Return(kDefaultGroupId));
|
||||
|
||||
const uint32_t k4kRepresentationId = 4001u;
|
||||
std::unique_ptr<MockRepresentation> fourk_representation(
|
||||
|
@ -609,16 +597,21 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
|||
EXPECT_CALL(*fourk_adaptation_set, AddRepresentation(_))
|
||||
.WillOnce(Return(fourk_representation.get()));
|
||||
|
||||
// Same group ID should be set.
|
||||
EXPECT_CALL(*fourk_adaptation_set, SetGroup(kExpectedGroupId));
|
||||
|
||||
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 grouped
|
||||
// together.
|
||||
TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) {
|
||||
// Even if the UUIDs match, video and audio AdaptationSets should not be
|
||||
// switchable.
|
||||
TEST_P(DashIopMpdNotifierTest,
|
||||
DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(
|
||||
|
@ -671,15 +664,6 @@ TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) {
|
|||
std::unique_ptr<MockAdaptationSet> audio_adaptation_set(
|
||||
new MockAdaptationSet(kAudioAdaptationSetId));
|
||||
|
||||
ON_CALL(*video_adaptation_set, Group())
|
||||
.WillByDefault(Return(kDefaultGroupId));
|
||||
ON_CALL(*audio_adaptation_set, Group())
|
||||
.WillByDefault(Return(kDefaultGroupId));
|
||||
|
||||
// Both AdaptationSets' groups should NOT be set.
|
||||
EXPECT_CALL(*video_adaptation_set, SetGroup(_)).Times(0);
|
||||
EXPECT_CALL(*audio_adaptation_set, SetGroup(_)).Times(0);
|
||||
|
||||
const uint32_t kVideoRepresentation = 8u;
|
||||
const uint32_t kAudioRepresentation = 9u;
|
||||
std::unique_ptr<MockRepresentation> video_representation(
|
||||
|
@ -708,6 +692,11 @@ TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) {
|
|||
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_P(DashIopMpdNotifierTest, UpdateEncryption) {
|
||||
|
|
|
@ -41,9 +41,6 @@ class MockAdaptationSet : public AdaptationSet {
|
|||
MOCK_METHOD1(AddRole, void(AdaptationSet::Role role));
|
||||
MOCK_METHOD1(ForceSetSegmentAlignment, void(bool segment_alignment));
|
||||
|
||||
MOCK_METHOD1(SetGroup, void(int group_number));
|
||||
MOCK_CONST_METHOD0(Group, int());
|
||||
|
||||
private:
|
||||
// Only for constructing the super class. Not used for testing.
|
||||
base::AtomicSequenceNumber sequence_counter_;
|
||||
|
|
|
@ -40,8 +40,6 @@ using xml::AdaptationSetXmlNode;
|
|||
|
||||
namespace {
|
||||
|
||||
const int kAdaptationSetGroupNotSet = -1;
|
||||
|
||||
AdaptationSet::Role MediaInfoTextTypeToRole(
|
||||
MediaInfo::TextInfo::TextType type) {
|
||||
switch (type) {
|
||||
|
@ -682,7 +680,6 @@ AdaptationSet::AdaptationSet(uint32_t adaptation_set_id,
|
|||
lang_(lang),
|
||||
mpd_options_(mpd_options),
|
||||
mpd_type_(mpd_type),
|
||||
group_(kAdaptationSetGroupNotSet),
|
||||
segments_aligned_(kSegmentAlignmentUnknown),
|
||||
force_set_segment_alignment_(false) {
|
||||
DCHECK(counter);
|
||||
|
@ -808,13 +805,22 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
|||
if (picture_aspect_ratio_.size() == 1)
|
||||
adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin());
|
||||
|
||||
if (group_ >= 0)
|
||||
adaptation_set.SetIntegerAttribute("group", group_);
|
||||
|
||||
if (!adaptation_set.AddContentProtectionElements(
|
||||
content_protection_elements_)) {
|
||||
return xml::scoped_xml_ptr<xmlNode>();
|
||||
}
|
||||
|
||||
std::string switching_ids;
|
||||
for (uint32_t id : adaptation_set_switching_ids_) {
|
||||
if (!switching_ids.empty())
|
||||
switching_ids += ',';
|
||||
switching_ids += base::UintToString(id);
|
||||
}
|
||||
if (!switching_ids.empty()) {
|
||||
adaptation_set.AddSupplementalProperty(
|
||||
"urn:mpeg:dash:adaptation-set-switching:2016", switching_ids);
|
||||
}
|
||||
|
||||
for (AdaptationSet::Role role : roles_)
|
||||
adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011", RoleToText(role));
|
||||
|
||||
|
@ -840,12 +846,8 @@ void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) {
|
|||
force_set_segment_alignment_ = true;
|
||||
}
|
||||
|
||||
void AdaptationSet::SetGroup(int group_number) {
|
||||
group_ = group_number;
|
||||
}
|
||||
|
||||
int AdaptationSet::Group() const {
|
||||
return group_;
|
||||
void AdaptationSet::AddAdaptationSetSwitching(uint32_t adaptation_set_id) {
|
||||
adaptation_set_switching_ids_.push_back(adaptation_set_id);
|
||||
}
|
||||
|
||||
// Check segmentAlignment for Live here. Storing all start_time and duration
|
||||
|
|
|
@ -232,15 +232,14 @@ class AdaptationSet {
|
|||
/// attribute.
|
||||
virtual void ForceSetSegmentAlignment(bool segment_alignment);
|
||||
|
||||
/// Sets the AdaptationSet@group attribute.
|
||||
/// Passing a negative value to this method will unset the attribute.
|
||||
/// Note that group=0 is a special group, as mentioned in the DASH MPD
|
||||
/// specification.
|
||||
/// @param group_number is the value of AdaptatoinSet@group.
|
||||
virtual void SetGroup(int group_number);
|
||||
/// Adds the id of the adaptation set this adaptation set can switch to.
|
||||
/// @param adaptation_set_id is the id of the switchable adaptation set.
|
||||
void AddAdaptationSetSwitching(uint32_t adaptation_set_id);
|
||||
|
||||
/// @return Returns the value for group. If not set, returns a negative value.
|
||||
virtual int Group() const;
|
||||
/// @return the ids of the adaptation sets this adaptation set can switch to.
|
||||
const std::vector<uint32_t>& adaptation_set_switching_ids() const {
|
||||
return adaptation_set_switching_ids_;
|
||||
}
|
||||
|
||||
// Must be unique in the Period.
|
||||
uint32_t id() const { return id_; }
|
||||
|
@ -348,10 +347,8 @@ class AdaptationSet {
|
|||
const MpdOptions& mpd_options_;
|
||||
const MpdBuilder::MpdType mpd_type_;
|
||||
|
||||
// The group attribute for the AdaptationSet. If the value is negative,
|
||||
// no group number is specified.
|
||||
// Note that group 0 is a special group number.
|
||||
int group_;
|
||||
// The ids of the adaptation sets this adaptation set can switch to.
|
||||
std::vector<uint32_t> adaptation_set_switching_ids_;
|
||||
|
||||
// Video widths and heights of Representations. Note that this is a set; if
|
||||
// there is only 1 resolution, then @width & @height should be set, otherwise
|
||||
|
|
|
@ -404,23 +404,39 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest {
|
|||
}
|
||||
};
|
||||
|
||||
// Verify that AdaptationSet@group can be set and unset.
|
||||
TEST_F(CommonMpdBuilderTest, SetAdaptationSetGroup) {
|
||||
base::AtomicSequenceNumber sequence_counter;
|
||||
auto adaptation_set =
|
||||
CreateAdaptationSet(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set->SetGroup(1);
|
||||
TEST_F(CommonMpdBuilderTest, AddAdaptationSetSwitching) {
|
||||
MpdBuilder mpd_builder(MpdBuilder::kStatic, MpdOptions());
|
||||
AdaptationSet* adaptation_set = mpd_builder.AddAdaptationSet("");
|
||||
adaptation_set->AddAdaptationSetSwitching(1);
|
||||
adaptation_set->AddAdaptationSetSwitching(2);
|
||||
adaptation_set->AddAdaptationSetSwitching(8);
|
||||
|
||||
xml::scoped_xml_ptr<xmlNode> xml_with_group(adaptation_set->GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeEqString("group", "1", xml_with_group.get()));
|
||||
|
||||
// Unset by passing a negative value.
|
||||
adaptation_set->SetGroup(-1);
|
||||
xml::scoped_xml_ptr<xmlNode> xml_without_group(adaptation_set->GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeNotSet("group", xml_without_group.get()));
|
||||
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||
// The empty contentType is sort of a side effect of being able to generate an
|
||||
// MPD without adding any Representations.
|
||||
const char kExpectedOutput[] =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n"
|
||||
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
|
||||
" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
|
||||
" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\"\n"
|
||||
" minBufferTime=\"PT2S\" type=\"static\"\n"
|
||||
" profiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\"\n"
|
||||
" mediaPresentationDuration=\"PT0S\">\n"
|
||||
" <Period id=\"0\">\n"
|
||||
" <AdaptationSet id=\"0\" contentType=\"\">\n"
|
||||
" <SupplementalProperty "
|
||||
"schemeIdUri=\"urn:mpeg:dash:adaptation-set-switching:2016\" "
|
||||
"value=\"1,2,8\"/>\n"
|
||||
" </AdaptationSet>\n"
|
||||
" </Period>\n"
|
||||
"</MPD>";
|
||||
std::string mpd_output;
|
||||
EXPECT_TRUE(mpd_builder.ToString(&mpd_output));
|
||||
ASSERT_TRUE(ValidateMpdSchema(mpd_output));
|
||||
EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output))
|
||||
<< "Expected " << kExpectedOutput << std::endl
|
||||
<< "Actual: " << mpd_output;
|
||||
}
|
||||
|
||||
// Verify that Representation::Init() works with all "required" fields of
|
||||
|
|
|
@ -166,6 +166,15 @@ bool RepresentationBaseXmlNode::AddContentProtectionElements(
|
|||
return true;
|
||||
}
|
||||
|
||||
void RepresentationBaseXmlNode::AddSupplementalProperty(
|
||||
const std::string& scheme_id_uri,
|
||||
const std::string& value) {
|
||||
XmlNode supplemental_property("SupplementalProperty");
|
||||
supplemental_property.SetStringAttribute("schemeIdUri", scheme_id_uri);
|
||||
supplemental_property.SetStringAttribute("value", value);
|
||||
AddChild(supplemental_property.PassScopedPtr());
|
||||
}
|
||||
|
||||
bool RepresentationBaseXmlNode::AddContentProtectionElement(
|
||||
const ContentProtectionElement& content_protection_element) {
|
||||
XmlNode content_protection_node("ContentProtection");
|
||||
|
|
|
@ -100,6 +100,11 @@ class RepresentationBaseXmlNode : public XmlNode {
|
|||
bool AddContentProtectionElements(
|
||||
const std::list<ContentProtectionElement>& content_protection_elements);
|
||||
|
||||
/// @param scheme_id_uri is content of the schemeIdUri attribute.
|
||||
/// @param value is the content of value attribute.
|
||||
void AddSupplementalProperty(const std::string& scheme_id_uri,
|
||||
const std::string& value);
|
||||
|
||||
protected:
|
||||
explicit RepresentationBaseXmlNode(const char* name);
|
||||
|
||||
|
|
Loading…
Reference in New Issue