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[0], 'is_encrypted: true')
|
||||||
self._AssertStreamInfo(self.output[1], '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.')
|
@unittest.skipUnless(test_env.has_aes_flags, 'Requires AES credentials.')
|
||||||
def testKeyRotationWithAes(self):
|
def testKeyRotationWithAes(self):
|
||||||
flags = self._GetFlags(widevine_encryption=True, key_rotation=True)
|
flags = self._GetFlags(widevine_encryption=True, key_rotation=True)
|
||||||
|
|
|
@ -15,8 +15,6 @@ namespace shaka {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const int kStartingGroupId = 1;
|
|
||||||
|
|
||||||
// The easiest way to check whether two protobufs are equal, is to compare the
|
// The easiest way to check whether two protobufs are equal, is to compare the
|
||||||
// serialized version.
|
// serialized version.
|
||||||
bool ProtectedContentEq(
|
bool ProtectedContentEq(
|
||||||
|
@ -50,8 +48,7 @@ DashIopMpdNotifier::DashIopMpdNotifier(
|
||||||
mpd_builder_(new MpdBuilder(dash_profile == kLiveProfile
|
mpd_builder_(new MpdBuilder(dash_profile == kLiveProfile
|
||||||
? MpdBuilder::kDynamic
|
? MpdBuilder::kDynamic
|
||||||
: MpdBuilder::kStatic,
|
: MpdBuilder::kStatic,
|
||||||
mpd_options)),
|
mpd_options)) {
|
||||||
next_group_id_(kStartingGroupId) {
|
|
||||||
DCHECK(dash_profile == kLiveProfile || dash_profile == kOnDemandProfile);
|
DCHECK(dash_profile == kLiveProfile || dash_profile == kOnDemandProfile);
|
||||||
for (size_t i = 0; i < base_urls.size(); ++i)
|
for (size_t i = 0; i < base_urls.size(); ++i)
|
||||||
mpd_builder_->AddBaseUrl(base_urls[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;
|
representation_id_to_adaptation_set_[representation->id()] = adaptation_set;
|
||||||
|
|
||||||
SetGroupId(key, adaptation_set);
|
SetAdaptationSetSwitching(key, adaptation_set);
|
||||||
|
|
||||||
*container_id = representation->id();
|
*container_id = representation->id();
|
||||||
DCHECK(!ContainsKey(representation_map_, 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
|
// Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the
|
||||||
// same UUIDs then those should be groupable.
|
// same UUIDs then those are switchable.
|
||||||
void DashIopMpdNotifier::SetGroupId(const std::string& key,
|
void DashIopMpdNotifier::SetAdaptationSetSwitching(
|
||||||
AdaptationSet* adaptation_set) {
|
const std::string& key,
|
||||||
if (adaptation_set->Group() >= 0) // @group already assigned.
|
AdaptationSet* adaptation_set) {
|
||||||
|
// This adaptation set is already visited.
|
||||||
|
if (!adaptation_set->adaptation_set_switching_ids().empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ProtectedContentMap::const_iterator protected_content_it =
|
ProtectedContentMap::const_iterator protected_content_it =
|
||||||
protected_content_map_.find(adaptation_set->id());
|
protected_content_map_.find(adaptation_set->id());
|
||||||
// Clear contents should be in one AdaptationSet, so no need to assign
|
// Clear contents should be in one AdaptationSet and may not be switchable
|
||||||
// @group.
|
// with encrypted contents.
|
||||||
if (protected_content_it == protected_content_map_.end()) {
|
if (protected_content_it == protected_content_map_.end()) {
|
||||||
DVLOG(1) << "No content protection set for AdaptationSet@id="
|
DVLOG(1) << "No content protection set for AdaptationSet@id="
|
||||||
<< adaptation_set->id();
|
<< adaptation_set->id();
|
||||||
|
@ -237,21 +236,15 @@ void DashIopMpdNotifier::SetGroupId(const std::string& key,
|
||||||
protected_content_map_[loop_adaptation_set_id];
|
protected_content_map_[loop_adaptation_set_id];
|
||||||
if (static_cast<int>(adaptation_set_uuids.size()) !=
|
if (static_cast<int>(adaptation_set_uuids.size()) !=
|
||||||
loop_protected_content.content_protection_entry().size()) {
|
loop_protected_content.content_protection_entry().size()) {
|
||||||
// Different number of UUIDs, cannot be grouped.
|
// Different number of UUIDs, may not be switchable.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adaptation_set_uuids == GetUUIDs(loop_protected_content)) {
|
if (adaptation_set_uuids == GetUUIDs(loop_protected_content)) {
|
||||||
AdaptationSet& uuid_match_adaptation_set = **adaptation_set_it;
|
AdaptationSet& uuid_match_adaptation_set = **adaptation_set_it;
|
||||||
// They match. These AdaptationSets can be in the same group. Break out.
|
// They match. These AdaptationSets are switchable.
|
||||||
if (uuid_match_adaptation_set.Group() >= 0) {
|
uuid_match_adaptation_set.AddAdaptationSetSwitching(adaptation_set->id());
|
||||||
adaptation_set->SetGroup(uuid_match_adaptation_set.Group());
|
adaptation_set->AddAdaptationSetSwitching(uuid_match_adaptation_set.id());
|
||||||
} else {
|
|
||||||
const int group_id = next_group_id_++;
|
|
||||||
uuid_match_adaptation_set.SetGroup(group_id);
|
|
||||||
adaptation_set->SetGroup(group_id);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,9 +73,10 @@ class DashIopMpdNotifier : public MpdNotifier {
|
||||||
AdaptationSet* GetAdaptationSetForMediaInfo(const std::string& key,
|
AdaptationSet* GetAdaptationSetForMediaInfo(const std::string& key,
|
||||||
const MediaInfo& media_info);
|
const MediaInfo& media_info);
|
||||||
|
|
||||||
// Sets a group id for |adaptation_set| if applicable.
|
// Sets adaptation set switching. If adaptation set switching is already
|
||||||
// If a group ID is already assigned, then this returns immediately.
|
// set, then this returns immediately.
|
||||||
void SetGroupId(const std::string& key, AdaptationSet* adaptation_set);
|
void SetAdaptationSetSwitching(const std::string& key,
|
||||||
|
AdaptationSet* adaptation_set);
|
||||||
|
|
||||||
// Helper function to get a new AdaptationSet; registers the values
|
// Helper function to get a new AdaptationSet; registers the values
|
||||||
// to the fields (maps) of the instance.
|
// to the fields (maps) of the instance.
|
||||||
|
@ -104,9 +105,6 @@ class DashIopMpdNotifier : public MpdNotifier {
|
||||||
std::unique_ptr<MpdBuilder> mpd_builder_;
|
std::unique_ptr<MpdBuilder> mpd_builder_;
|
||||||
base::Lock lock_;
|
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.
|
// Maps Representation ID to AdaptationSet. This is for updating the PSSH.
|
||||||
std::map<uint32_t, AdaptationSet*> representation_id_to_adaptation_set_;
|
std::map<uint32_t, AdaptationSet*> representation_id_to_adaptation_set_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,9 +18,11 @@ namespace shaka {
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::Eq;
|
using ::testing::Eq;
|
||||||
|
using ::testing::ElementsAre;
|
||||||
using ::testing::InSequence;
|
using ::testing::InSequence;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
using ::testing::StrEq;
|
using ::testing::StrEq;
|
||||||
|
using ::testing::UnorderedElementsAre;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -37,7 +39,6 @@ const char kValidMediaInfo[] =
|
||||||
"container_type: 1\n";
|
"container_type: 1\n";
|
||||||
const uint32_t kDefaultAdaptationSetId = 0u;
|
const uint32_t kDefaultAdaptationSetId = 0u;
|
||||||
const uint32_t kDefaultRepresentationId = 1u;
|
const uint32_t kDefaultRepresentationId = 1u;
|
||||||
const int kDefaultGroupId = -1;
|
|
||||||
|
|
||||||
bool ElementEqual(const Element& lhs, const Element& rhs) {
|
bool ElementEqual(const Element& lhs, const Element& rhs) {
|
||||||
const bool all_equal_except_sublement_check =
|
const bool all_equal_except_sublement_check =
|
||||||
|
@ -102,8 +103,6 @@ class DashIopMpdNotifierTest
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
|
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
|
||||||
output_path_ = temp_file_path_.AsUTF8Unsafe();
|
output_path_ = temp_file_path_.AsUTF8Unsafe();
|
||||||
ON_CALL(*default_mock_adaptation_set_, Group())
|
|
||||||
.WillByDefault(Return(kDefaultGroupId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TearDown() override {
|
void TearDown() override {
|
||||||
|
@ -218,7 +217,7 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewTextContainer) {
|
||||||
// Verify VOD NotifyNewContainer() operation works with different
|
// Verify VOD NotifyNewContainer() operation works with different
|
||||||
// MediaInfo::ProtectedContent.
|
// MediaInfo::ProtectedContent.
|
||||||
// Two AdaptationSets should be created.
|
// Two AdaptationSets should be created.
|
||||||
// Different DRM so they won't be grouped.
|
// AdaptationSets with different DRM won't be switchable.
|
||||||
TEST_P(DashIopMpdNotifierTest,
|
TEST_P(DashIopMpdNotifierTest,
|
||||||
NotifyNewContainersWithDifferentProtectedContent) {
|
NotifyNewContainersWithDifferentProtectedContent) {
|
||||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||||
|
@ -295,11 +294,6 @@ TEST_P(DashIopMpdNotifierTest,
|
||||||
std::unique_ptr<MockAdaptationSet> hd_adaptation_set(
|
std::unique_ptr<MockAdaptationSet> hd_adaptation_set(
|
||||||
new MockAdaptationSet(kHdAdaptationSetId));
|
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 kSdRepresentation = 4u;
|
||||||
const uint32_t kHdRepresentation = 5u;
|
const uint32_t kHdRepresentation = 5u;
|
||||||
std::unique_ptr<MockRepresentation> sd_representation(
|
std::unique_ptr<MockRepresentation> sd_representation(
|
||||||
|
@ -336,6 +330,9 @@ TEST_P(DashIopMpdNotifierTest,
|
||||||
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
|
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||||
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
|
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
|
// Verify VOD NotifyNewContainer() operation works with same
|
||||||
|
@ -410,9 +407,6 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewContainersWithSameProtectedContent) {
|
||||||
std::unique_ptr<MockRepresentation> hd_representation(
|
std::unique_ptr<MockRepresentation> hd_representation(
|
||||||
new MockRepresentation(kHdRepresentation));
|
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;
|
InSequence in_sequence;
|
||||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
||||||
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
|
@ -440,6 +434,10 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewContainersWithSameProtectedContent) {
|
||||||
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
|
ConvertToMediaInfo(kSdProtectedContent), &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||||
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
|
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.
|
// 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
|
// 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.
|
// This is a long test.
|
||||||
// Basically this
|
// Basically this
|
||||||
// 1. Add an SD protected content. This should make an AdaptationSet.
|
// 1. Add an SD protected content. This should make an AdaptationSet.
|
||||||
// 2. Add an HD protected content. This should make another AdaptationSet that
|
// 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
|
// is different from the SD version. SD AdaptationSet and HD AdaptationSet
|
||||||
// group ID assigned.
|
// should be switchable.
|
||||||
// 3. Add a 4k protected content. This should also make a new AdaptationSet.
|
// 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
|
// It should be switchable with SD/HD AdaptationSet.
|
||||||
// different path).
|
TEST_P(DashIopMpdNotifierTest, SetAdaptationSetSwitching) {
|
||||||
TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
|
||||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||||
empty_base_urls_, output_path_);
|
empty_base_urls_, output_path_);
|
||||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(
|
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(
|
||||||
|
@ -530,9 +527,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
||||||
std::unique_ptr<MockAdaptationSet> hd_adaptation_set(
|
std::unique_ptr<MockAdaptationSet> hd_adaptation_set(
|
||||||
new MockAdaptationSet(kHdAdaptationSetId));
|
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 kSdRepresentation = 4u;
|
||||||
const uint32_t kHdRepresentation = 5u;
|
const uint32_t kHdRepresentation = 5u;
|
||||||
std::unique_ptr<MockRepresentation> sd_representation(
|
std::unique_ptr<MockRepresentation> sd_representation(
|
||||||
|
@ -553,11 +547,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
||||||
EXPECT_CALL(*hd_adaptation_set, AddRepresentation(_))
|
EXPECT_CALL(*hd_adaptation_set, AddRepresentation(_))
|
||||||
.WillOnce(Return(hd_representation.get()));
|
.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.
|
// This is not very nice but we need it for settings expectations later.
|
||||||
MockMpdBuilder* mock_mpd_builder_raw = mock_mpd_builder.get();
|
MockMpdBuilder* mock_mpd_builder_raw = mock_mpd_builder.get();
|
||||||
uint32_t unused_container_id;
|
uint32_t unused_container_id;
|
||||||
|
@ -567,12 +556,13 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||||
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
|
ConvertToMediaInfo(kHdProtectedContent), &unused_container_id));
|
||||||
|
|
||||||
// Now that the group IDs are set Group() returns kExpectedGroupId.
|
EXPECT_THAT(sd_adaptation_set->adaptation_set_switching_ids(),
|
||||||
ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kExpectedGroupId));
|
ElementsAre(kHdAdaptationSetId));
|
||||||
ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kExpectedGroupId));
|
EXPECT_THAT(hd_adaptation_set->adaptation_set_switching_ids(),
|
||||||
|
ElementsAre(kSdAdaptationSetId));
|
||||||
|
|
||||||
// Add another content that has the same protected content and make sure that
|
// 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[] =
|
const char k4kProtectedContent[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
" codec: 'avc1'\n"
|
" codec: 'avc1'\n"
|
||||||
|
@ -596,8 +586,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
||||||
const uint32_t k4kAdaptationSetId = 4000u;
|
const uint32_t k4kAdaptationSetId = 4000u;
|
||||||
std::unique_ptr<MockAdaptationSet> fourk_adaptation_set(
|
std::unique_ptr<MockAdaptationSet> fourk_adaptation_set(
|
||||||
new MockAdaptationSet(k4kAdaptationSetId));
|
new MockAdaptationSet(k4kAdaptationSetId));
|
||||||
ON_CALL(*fourk_adaptation_set, Group())
|
|
||||||
.WillByDefault(Return(kDefaultGroupId));
|
|
||||||
|
|
||||||
const uint32_t k4kRepresentationId = 4001u;
|
const uint32_t k4kRepresentationId = 4001u;
|
||||||
std::unique_ptr<MockRepresentation> fourk_representation(
|
std::unique_ptr<MockRepresentation> fourk_representation(
|
||||||
|
@ -609,16 +597,21 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) {
|
||||||
EXPECT_CALL(*fourk_adaptation_set, AddRepresentation(_))
|
EXPECT_CALL(*fourk_adaptation_set, AddRepresentation(_))
|
||||||
.WillOnce(Return(fourk_representation.get()));
|
.WillOnce(Return(fourk_representation.get()));
|
||||||
|
|
||||||
// Same group ID should be set.
|
|
||||||
EXPECT_CALL(*fourk_adaptation_set, SetGroup(kExpectedGroupId));
|
|
||||||
|
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||||
ConvertToMediaInfo(k4kProtectedContent), &unused_container_id));
|
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
|
// Even if the UUIDs match, video and audio AdaptationSets should not be
|
||||||
// together.
|
// switchable.
|
||||||
TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) {
|
TEST_P(DashIopMpdNotifierTest,
|
||||||
|
DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
|
||||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||||
empty_base_urls_, output_path_);
|
empty_base_urls_, output_path_);
|
||||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(
|
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(
|
||||||
|
@ -671,15 +664,6 @@ TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) {
|
||||||
std::unique_ptr<MockAdaptationSet> audio_adaptation_set(
|
std::unique_ptr<MockAdaptationSet> audio_adaptation_set(
|
||||||
new MockAdaptationSet(kAudioAdaptationSetId));
|
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 kVideoRepresentation = 8u;
|
||||||
const uint32_t kAudioRepresentation = 9u;
|
const uint32_t kAudioRepresentation = 9u;
|
||||||
std::unique_ptr<MockRepresentation> video_representation(
|
std::unique_ptr<MockRepresentation> video_representation(
|
||||||
|
@ -708,6 +692,11 @@ TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) {
|
||||||
ConvertToMediaInfo(kVideoContent), &unused_container_id));
|
ConvertToMediaInfo(kVideoContent), &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(notifier.NotifyNewContainer(
|
||||||
ConvertToMediaInfo(kAudioContent), &unused_container_id));
|
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) {
|
TEST_P(DashIopMpdNotifierTest, UpdateEncryption) {
|
||||||
|
|
|
@ -41,9 +41,6 @@ class MockAdaptationSet : public AdaptationSet {
|
||||||
MOCK_METHOD1(AddRole, void(AdaptationSet::Role role));
|
MOCK_METHOD1(AddRole, void(AdaptationSet::Role role));
|
||||||
MOCK_METHOD1(ForceSetSegmentAlignment, void(bool segment_alignment));
|
MOCK_METHOD1(ForceSetSegmentAlignment, void(bool segment_alignment));
|
||||||
|
|
||||||
MOCK_METHOD1(SetGroup, void(int group_number));
|
|
||||||
MOCK_CONST_METHOD0(Group, int());
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Only for constructing the super class. Not used for testing.
|
// Only for constructing the super class. Not used for testing.
|
||||||
base::AtomicSequenceNumber sequence_counter_;
|
base::AtomicSequenceNumber sequence_counter_;
|
||||||
|
|
|
@ -40,8 +40,6 @@ using xml::AdaptationSetXmlNode;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const int kAdaptationSetGroupNotSet = -1;
|
|
||||||
|
|
||||||
AdaptationSet::Role MediaInfoTextTypeToRole(
|
AdaptationSet::Role MediaInfoTextTypeToRole(
|
||||||
MediaInfo::TextInfo::TextType type) {
|
MediaInfo::TextInfo::TextType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -682,7 +680,6 @@ AdaptationSet::AdaptationSet(uint32_t adaptation_set_id,
|
||||||
lang_(lang),
|
lang_(lang),
|
||||||
mpd_options_(mpd_options),
|
mpd_options_(mpd_options),
|
||||||
mpd_type_(mpd_type),
|
mpd_type_(mpd_type),
|
||||||
group_(kAdaptationSetGroupNotSet),
|
|
||||||
segments_aligned_(kSegmentAlignmentUnknown),
|
segments_aligned_(kSegmentAlignmentUnknown),
|
||||||
force_set_segment_alignment_(false) {
|
force_set_segment_alignment_(false) {
|
||||||
DCHECK(counter);
|
DCHECK(counter);
|
||||||
|
@ -808,13 +805,22 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
||||||
if (picture_aspect_ratio_.size() == 1)
|
if (picture_aspect_ratio_.size() == 1)
|
||||||
adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin());
|
adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin());
|
||||||
|
|
||||||
if (group_ >= 0)
|
|
||||||
adaptation_set.SetIntegerAttribute("group", group_);
|
|
||||||
|
|
||||||
if (!adaptation_set.AddContentProtectionElements(
|
if (!adaptation_set.AddContentProtectionElements(
|
||||||
content_protection_elements_)) {
|
content_protection_elements_)) {
|
||||||
return xml::scoped_xml_ptr<xmlNode>();
|
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_)
|
for (AdaptationSet::Role role : roles_)
|
||||||
adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011", RoleToText(role));
|
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;
|
force_set_segment_alignment_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdaptationSet::SetGroup(int group_number) {
|
void AdaptationSet::AddAdaptationSetSwitching(uint32_t adaptation_set_id) {
|
||||||
group_ = group_number;
|
adaptation_set_switching_ids_.push_back(adaptation_set_id);
|
||||||
}
|
|
||||||
|
|
||||||
int AdaptationSet::Group() const {
|
|
||||||
return group_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check segmentAlignment for Live here. Storing all start_time and duration
|
// Check segmentAlignment for Live here. Storing all start_time and duration
|
||||||
|
|
|
@ -232,15 +232,14 @@ class AdaptationSet {
|
||||||
/// attribute.
|
/// attribute.
|
||||||
virtual void ForceSetSegmentAlignment(bool segment_alignment);
|
virtual void ForceSetSegmentAlignment(bool segment_alignment);
|
||||||
|
|
||||||
/// Sets the AdaptationSet@group attribute.
|
/// Adds the id of the adaptation set this adaptation set can switch to.
|
||||||
/// Passing a negative value to this method will unset the attribute.
|
/// @param adaptation_set_id is the id of the switchable adaptation set.
|
||||||
/// Note that group=0 is a special group, as mentioned in the DASH MPD
|
void AddAdaptationSetSwitching(uint32_t adaptation_set_id);
|
||||||
/// specification.
|
|
||||||
/// @param group_number is the value of AdaptatoinSet@group.
|
|
||||||
virtual void SetGroup(int group_number);
|
|
||||||
|
|
||||||
/// @return Returns the value for group. If not set, returns a negative value.
|
/// @return the ids of the adaptation sets this adaptation set can switch to.
|
||||||
virtual int Group() const;
|
const std::vector<uint32_t>& adaptation_set_switching_ids() const {
|
||||||
|
return adaptation_set_switching_ids_;
|
||||||
|
}
|
||||||
|
|
||||||
// Must be unique in the Period.
|
// Must be unique in the Period.
|
||||||
uint32_t id() const { return id_; }
|
uint32_t id() const { return id_; }
|
||||||
|
@ -348,10 +347,8 @@ class AdaptationSet {
|
||||||
const MpdOptions& mpd_options_;
|
const MpdOptions& mpd_options_;
|
||||||
const MpdBuilder::MpdType mpd_type_;
|
const MpdBuilder::MpdType mpd_type_;
|
||||||
|
|
||||||
// The group attribute for the AdaptationSet. If the value is negative,
|
// The ids of the adaptation sets this adaptation set can switch to.
|
||||||
// no group number is specified.
|
std::vector<uint32_t> adaptation_set_switching_ids_;
|
||||||
// Note that group 0 is a special group number.
|
|
||||||
int group_;
|
|
||||||
|
|
||||||
// Video widths and heights of Representations. Note that this is a set; if
|
// 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
|
// 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, AddAdaptationSetSwitching) {
|
||||||
TEST_F(CommonMpdBuilderTest, SetAdaptationSetGroup) {
|
MpdBuilder mpd_builder(MpdBuilder::kStatic, MpdOptions());
|
||||||
base::AtomicSequenceNumber sequence_counter;
|
AdaptationSet* adaptation_set = mpd_builder.AddAdaptationSet("");
|
||||||
auto adaptation_set =
|
adaptation_set->AddAdaptationSetSwitching(1);
|
||||||
CreateAdaptationSet(kAnyAdaptationSetId, "", MpdOptions(),
|
adaptation_set->AddAdaptationSetSwitching(2);
|
||||||
MpdBuilder::kStatic, &sequence_counter);
|
adaptation_set->AddAdaptationSetSwitching(8);
|
||||||
adaptation_set->SetGroup(1);
|
|
||||||
|
|
||||||
xml::scoped_xml_ptr<xmlNode> xml_with_group(adaptation_set->GetXml());
|
xml::scoped_xml_ptr<xmlNode> adaptation_set_xml(adaptation_set->GetXml());
|
||||||
EXPECT_NO_FATAL_FAILURE(
|
// The empty contentType is sort of a side effect of being able to generate an
|
||||||
ExpectAttributeEqString("group", "1", xml_with_group.get()));
|
// MPD without adding any Representations.
|
||||||
|
const char kExpectedOutput[] =
|
||||||
// Unset by passing a negative value.
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
adaptation_set->SetGroup(-1);
|
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n"
|
||||||
xml::scoped_xml_ptr<xmlNode> xml_without_group(adaptation_set->GetXml());
|
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
|
||||||
EXPECT_NO_FATAL_FAILURE(
|
" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
|
||||||
ExpectAttributeNotSet("group", xml_without_group.get()));
|
" 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
|
// Verify that Representation::Init() works with all "required" fields of
|
||||||
|
|
|
@ -166,6 +166,15 @@ bool RepresentationBaseXmlNode::AddContentProtectionElements(
|
||||||
return true;
|
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(
|
bool RepresentationBaseXmlNode::AddContentProtectionElement(
|
||||||
const ContentProtectionElement& content_protection_element) {
|
const ContentProtectionElement& content_protection_element) {
|
||||||
XmlNode content_protection_node("ContentProtection");
|
XmlNode content_protection_node("ContentProtection");
|
||||||
|
|
|
@ -100,6 +100,11 @@ class RepresentationBaseXmlNode : public XmlNode {
|
||||||
bool AddContentProtectionElements(
|
bool AddContentProtectionElements(
|
||||||
const std::list<ContentProtectionElement>& content_protection_elements);
|
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:
|
protected:
|
||||||
explicit RepresentationBaseXmlNode(const char* name);
|
explicit RepresentationBaseXmlNode(const char* name);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue