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:
parent
99469834e8
commit
718fce068d
|
@ -12,6 +12,7 @@
|
||||||
#include "packager/base/strings/string_number_conversions.h"
|
#include "packager/base/strings/string_number_conversions.h"
|
||||||
#include "packager/media/base/language_utils.h"
|
#include "packager/media/base/language_utils.h"
|
||||||
#include "packager/mpd/base/media_info.pb.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/mpd_utils.h"
|
||||||
#include "packager/mpd/base/representation.h"
|
#include "packager/mpd/base/representation.h"
|
||||||
#include "packager/mpd/base/xml/xml_node.h"
|
#include "packager/mpd/base/xml/xml_node.h"
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
//
|
//
|
||||||
/// All the methods that are virtual are virtual for mocking.
|
/// 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 <stdint.h>
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
@ -15,7 +18,6 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "packager/base/atomic_sequence_num.h"
|
#include "packager/base/atomic_sequence_num.h"
|
||||||
#include "packager/mpd/base/mpd_options.h"
|
|
||||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
@ -24,6 +26,7 @@ class MediaInfo;
|
||||||
class Representation;
|
class Representation;
|
||||||
|
|
||||||
struct ContentProtectionElement;
|
struct ContentProtectionElement;
|
||||||
|
struct MpdOptions;
|
||||||
|
|
||||||
namespace xml {
|
namespace xml {
|
||||||
class XmlNode;
|
class XmlNode;
|
||||||
|
@ -166,7 +169,7 @@ class AdaptationSet {
|
||||||
AdaptationSet(const AdaptationSet&) = delete;
|
AdaptationSet(const AdaptationSet&) = delete;
|
||||||
AdaptationSet& operator=(const AdaptationSet&) = delete;
|
AdaptationSet& operator=(const AdaptationSet&) = delete;
|
||||||
|
|
||||||
friend class MpdBuilder;
|
friend class Period;
|
||||||
friend class AdaptationSetTest;
|
friend class AdaptationSetTest;
|
||||||
|
|
||||||
// kSegmentAlignmentUnknown means that it is uncertain if the
|
// kSegmentAlignmentUnknown means that it is uncertain if the
|
||||||
|
@ -276,3 +279,5 @@ class AdaptationSet {
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
||||||
|
#endif // PACKAGER_MPD_BASE_ADAPTATION_SET_H_
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/mpd/base/content_protection_element.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/base/representation.h"
|
||||||
#include "packager/mpd/test/mpd_builder_test_helper.h"
|
#include "packager/mpd/test/mpd_builder_test_helper.h"
|
||||||
#include "packager/mpd/test/xml_compare.h"
|
#include "packager/mpd/test/xml_compare.h"
|
||||||
|
|
|
@ -10,36 +10,15 @@
|
||||||
#include "packager/mpd/base/adaptation_set.h"
|
#include "packager/mpd/base/adaptation_set.h"
|
||||||
#include "packager/mpd/base/media_info.pb.h"
|
#include "packager/mpd/base/media_info.pb.h"
|
||||||
#include "packager/mpd/base/mpd_notifier_util.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"
|
#include "packager/mpd/base/representation.h"
|
||||||
|
|
||||||
namespace shaka {
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
const bool kContentProtectionInAdaptationSet = true;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
|
||||||
DashIopMpdNotifier::DashIopMpdNotifier(const MpdOptions& mpd_options)
|
DashIopMpdNotifier::DashIopMpdNotifier(const MpdOptions& mpd_options)
|
||||||
: MpdNotifier(mpd_options),
|
: MpdNotifier(mpd_options),
|
||||||
output_path_(mpd_options.mpd_params.mpd_output),
|
output_path_(mpd_options.mpd_params.mpd_output),
|
||||||
|
@ -63,7 +42,10 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
base::AutoLock auto_lock(lock_);
|
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);
|
DCHECK(adaptation_set);
|
||||||
|
|
||||||
MediaInfo adjusted_media_info(media_info);
|
MediaInfo adjusted_media_info(media_info);
|
||||||
|
@ -84,7 +66,7 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
||||||
bool DashIopMpdNotifier::NotifySampleDuration(uint32_t container_id,
|
bool DashIopMpdNotifier::NotifySampleDuration(uint32_t container_id,
|
||||||
uint32_t sample_duration) {
|
uint32_t sample_duration) {
|
||||||
base::AutoLock auto_lock(lock_);
|
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()) {
|
if (it == representation_map_.end()) {
|
||||||
LOG(ERROR) << "Unexpected container_id: " << container_id;
|
LOG(ERROR) << "Unexpected container_id: " << container_id;
|
||||||
return false;
|
return false;
|
||||||
|
@ -98,7 +80,7 @@ bool DashIopMpdNotifier::NotifyNewSegment(uint32_t container_id,
|
||||||
uint64_t duration,
|
uint64_t duration,
|
||||||
uint64_t size) {
|
uint64_t size) {
|
||||||
base::AutoLock auto_lock(lock_);
|
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()) {
|
if (it == representation_map_.end()) {
|
||||||
LOG(ERROR) << "Unexpected container_id: " << container_id;
|
LOG(ERROR) << "Unexpected container_id: " << container_id;
|
||||||
return false;
|
return false;
|
||||||
|
@ -113,7 +95,7 @@ bool DashIopMpdNotifier::NotifyEncryptionUpdate(
|
||||||
const std::vector<uint8_t>& new_key_id,
|
const std::vector<uint8_t>& new_key_id,
|
||||||
const std::vector<uint8_t>& new_pssh) {
|
const std::vector<uint8_t>& new_pssh) {
|
||||||
base::AutoLock auto_lock(lock_);
|
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()) {
|
if (it == representation_map_.end()) {
|
||||||
LOG(ERROR) << "Unexpected container_id: " << container_id;
|
LOG(ERROR) << "Unexpected container_id: " << container_id;
|
||||||
return false;
|
return false;
|
||||||
|
@ -141,127 +123,4 @@ bool DashIopMpdNotifier::Flush() {
|
||||||
return WriteMpdToFile(output_path_, mpd_builder_.get());
|
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
|
} // namespace shaka
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
#include "packager/mpd/base/mpd_notifier.h"
|
#include "packager/mpd/base/mpd_notifier.h"
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -22,6 +21,7 @@ namespace shaka {
|
||||||
|
|
||||||
class AdaptationSet;
|
class AdaptationSet;
|
||||||
class MpdBuilder;
|
class MpdBuilder;
|
||||||
|
class Period;
|
||||||
class Representation;
|
class Representation;
|
||||||
|
|
||||||
/// This class is an MpdNotifier which will try its best to generate a
|
/// This class is an MpdNotifier which will try its best to generate a
|
||||||
|
@ -59,31 +59,6 @@ class DashIopMpdNotifier : public MpdNotifier {
|
||||||
private:
|
private:
|
||||||
friend class DashIopMpdNotifierTest;
|
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.
|
// Testing only method. Returns a pointer to MpdBuilder.
|
||||||
MpdBuilder* MpdBuilderForTesting() const {
|
MpdBuilder* MpdBuilderForTesting() const {
|
||||||
return mpd_builder_.get();
|
return mpd_builder_.get();
|
||||||
|
@ -94,39 +69,14 @@ class DashIopMpdNotifier : public MpdNotifier {
|
||||||
mpd_builder_ = std::move(mpd_builder);
|
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.
|
// MPD output path.
|
||||||
std::string output_path_;
|
std::string output_path_;
|
||||||
std::unique_ptr<MpdBuilder> mpd_builder_;
|
std::unique_ptr<MpdBuilder> mpd_builder_;
|
||||||
|
Period* period_ = nullptr; // owned by |mpd_builder_|.
|
||||||
base::Lock lock_;
|
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.
|
// 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_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
|
#include <google/protobuf/util/message_differencer.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/base/files/file_path.h"
|
#include "packager/base/files/file_path.h"
|
||||||
|
@ -17,70 +18,19 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
|
using ::testing::AnyOf;
|
||||||
using ::testing::Eq;
|
using ::testing::Eq;
|
||||||
using ::testing::ElementsAre;
|
|
||||||
using ::testing::InSequence;
|
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
using ::testing::StrEq;
|
using ::testing::StrEq;
|
||||||
using ::testing::UnorderedElementsAre;
|
|
||||||
|
|
||||||
namespace {
|
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 kDefaultAdaptationSetId = 0u;
|
||||||
const uint32_t kDefaultRepresentationId = 1u;
|
const uint32_t kDefaultRepresentationId = 1u;
|
||||||
|
const bool kContentProtectionInAdaptationSet = true;
|
||||||
|
|
||||||
bool ElementEqual(const Element& lhs, const Element& rhs) {
|
MATCHER_P(EqualsProto, message, "") {
|
||||||
const bool all_equal_except_sublement_check =
|
return ::google::protobuf::util::MessageDifferencer::Equals(arg, message);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -94,7 +44,8 @@ MATCHER_P(ContentProtectionElementEq, expected, "") {
|
||||||
class DashIopMpdNotifierTest : public ::testing::Test {
|
class DashIopMpdNotifierTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
DashIopMpdNotifierTest()
|
DashIopMpdNotifierTest()
|
||||||
: default_mock_adaptation_set_(
|
: default_mock_period_(new MockPeriod),
|
||||||
|
default_mock_adaptation_set_(
|
||||||
new MockAdaptationSet(kDefaultAdaptationSetId)),
|
new MockAdaptationSet(kDefaultAdaptationSetId)),
|
||||||
default_mock_representation_(
|
default_mock_representation_(
|
||||||
new MockRepresentation(kDefaultRepresentationId)) {}
|
new MockRepresentation(kDefaultRepresentationId)) {}
|
||||||
|
@ -102,6 +53,24 @@ class DashIopMpdNotifierTest : public ::testing::Test {
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
|
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
|
||||||
empty_mpd_option_.mpd_params.mpd_output = temp_file_path_.AsUTF8Unsafe();
|
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 {
|
void TearDown() override {
|
||||||
|
@ -113,16 +82,23 @@ class DashIopMpdNotifierTest : public ::testing::Test {
|
||||||
notifier->SetMpdBuilderForTesting(std::move(mpd_builder));
|
notifier->SetMpdBuilderForTesting(std::move(mpd_builder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
// Empty mpd options except with output path specified, so that
|
// Empty mpd options except with output path specified, so that
|
||||||
// WriteMpdToFile() doesn't crash.
|
// WriteMpdToFile() doesn't crash.
|
||||||
MpdOptions empty_mpd_option_;
|
MpdOptions empty_mpd_option_;
|
||||||
|
|
||||||
// Default mocks that can be used for the tests.
|
// Default mocks that can be used for the tests.
|
||||||
// IOW, if a test only requires one instance of
|
// 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<MockAdaptationSet> default_mock_adaptation_set_;
|
||||||
std::unique_ptr<MockRepresentation> default_mock_representation_;
|
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:
|
private:
|
||||||
base::FilePath temp_file_path_;
|
base::FilePath temp_file_path_;
|
||||||
};
|
};
|
||||||
|
@ -134,9 +110,12 @@ TEST_F(DashIopMpdNotifierTest, NotifyNewContainer) {
|
||||||
|
|
||||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
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()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRole(_)).Times(0);
|
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||||
.WillOnce(Return(default_mock_representation_.get()));
|
.WillOnce(Return(default_mock_representation_.get()));
|
||||||
|
|
||||||
|
@ -146,616 +125,48 @@ TEST_F(DashIopMpdNotifierTest, NotifyNewContainer) {
|
||||||
|
|
||||||
uint32_t unused_container_id;
|
uint32_t unused_container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
EXPECT_TRUE(
|
||||||
&unused_container_id));
|
notifier.NotifyNewContainer(valid_media_info1_, &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.Flush());
|
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(¬ifier, 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(¬ifier, 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(¬ifier, 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(¬ifier, 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.
|
// AddContentProtection() should not work and should always return false.
|
||||||
TEST_F(DashIopMpdNotifierTest, AddContentProtection) {
|
TEST_F(DashIopMpdNotifierTest, AddContentProtection) {
|
||||||
DashIopMpdNotifier notifier(empty_mpd_option_);
|
DashIopMpdNotifier notifier(empty_mpd_option_);
|
||||||
|
|
||||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
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()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||||
.WillOnce(Return(default_mock_representation_.get()));
|
.WillOnce(Return(default_mock_representation_.get()));
|
||||||
|
|
||||||
uint32_t container_id;
|
uint32_t container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
&container_id));
|
|
||||||
|
|
||||||
ContentProtectionElement empty_content_protection_element;
|
ContentProtectionElement empty_content_protection_element;
|
||||||
EXPECT_FALSE(notifier.AddContentProtectionElement(
|
EXPECT_FALSE(notifier.AddContentProtectionElement(
|
||||||
container_id, empty_content_protection_element));
|
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(¬ifier, 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(¬ifier, 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) {
|
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_);
|
DashIopMpdNotifier notifier(empty_mpd_option_);
|
||||||
|
|
||||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
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()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRole(_)).Times(0);
|
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||||
.WillOnce(Return(default_mock_representation_.get()));
|
.WillOnce(Return(default_mock_representation_.get()));
|
||||||
|
|
||||||
uint32_t container_id;
|
uint32_t container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kProtectedContent),
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
&container_id));
|
|
||||||
|
|
||||||
::testing::Mock::VerifyAndClearExpectations(
|
::testing::Mock::VerifyAndClearExpectations(
|
||||||
default_mock_adaptation_set_.get());
|
default_mock_adaptation_set_.get());
|
||||||
|
@ -783,73 +194,19 @@ TEST_F(DashIopMpdNotifierTest, UpdateEncryption) {
|
||||||
TEST_F(DashIopMpdNotifierTest, NotifyNewContainerAndSampleDurationNoMock) {
|
TEST_F(DashIopMpdNotifierTest, NotifyNewContainerAndSampleDurationNoMock) {
|
||||||
DashIopMpdNotifier notifier(empty_mpd_option_);
|
DashIopMpdNotifier notifier(empty_mpd_option_);
|
||||||
uint32_t container_id;
|
uint32_t container_id;
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
&container_id));
|
|
||||||
const uint32_t kAnySampleDuration = 1000;
|
const uint32_t kAnySampleDuration = 1000;
|
||||||
EXPECT_TRUE(notifier.NotifySampleDuration(container_id, kAnySampleDuration));
|
EXPECT_TRUE(notifier.NotifySampleDuration(container_id, kAnySampleDuration));
|
||||||
EXPECT_TRUE(notifier.Flush());
|
EXPECT_TRUE(notifier.Flush());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't put different audio languages or codecs in the same AdaptationSet.
|
// Test multiple media info with some belongs to the same AdaptationSets.
|
||||||
TEST_F(DashIopMpdNotifierTest, SplitAdaptationSetsByLanguageAndCodec) {
|
TEST_F(DashIopMpdNotifierTest, MultipleMediaInfo) {
|
||||||
// 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";
|
|
||||||
|
|
||||||
DashIopMpdNotifier notifier(empty_mpd_option_);
|
DashIopMpdNotifier notifier(empty_mpd_option_);
|
||||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
||||||
|
|
||||||
std::unique_ptr<MockAdaptationSet> adaptation_set1(new MockAdaptationSet(1));
|
std::unique_ptr<MockAdaptationSet> adaptation_set1(new MockAdaptationSet(1));
|
||||||
std::unique_ptr<MockAdaptationSet> adaptation_set2(new MockAdaptationSet(2));
|
std::unique_ptr<MockAdaptationSet> adaptation_set2(new MockAdaptationSet(2));
|
||||||
std::unique_ptr<MockAdaptationSet> adaptation_set3(new MockAdaptationSet(3));
|
|
||||||
|
|
||||||
std::unique_ptr<MockRepresentation> representation1(
|
std::unique_ptr<MockRepresentation> representation1(
|
||||||
new MockRepresentation(1));
|
new MockRepresentation(1));
|
||||||
|
@ -857,35 +214,39 @@ TEST_F(DashIopMpdNotifierTest, SplitAdaptationSetsByLanguageAndCodec) {
|
||||||
new MockRepresentation(2));
|
new MockRepresentation(2));
|
||||||
std::unique_ptr<MockRepresentation> representation3(
|
std::unique_ptr<MockRepresentation> representation3(
|
||||||
new MockRepresentation(3));
|
new MockRepresentation(3));
|
||||||
std::unique_ptr<MockRepresentation> representation4(
|
|
||||||
new MockRepresentation(4));
|
|
||||||
|
|
||||||
// We expect three AdaptationSets.
|
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
|
||||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
.WillOnce(Return(default_mock_period_.get()));
|
||||||
.WillOnce(Return(adaptation_set1.get()))
|
|
||||||
.WillOnce(Return(adaptation_set2.get()))
|
EXPECT_CALL(*default_mock_period_,
|
||||||
.WillOnce(Return(adaptation_set3.get()));
|
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
|
||||||
// The first AdaptationSet should have Eng MP4, one Representation.
|
.WillOnce(Return(adaptation_set1.get()));
|
||||||
EXPECT_CALL(*adaptation_set1, AddRepresentation(_))
|
EXPECT_CALL(*adaptation_set1,
|
||||||
|
AddRepresentation(EqualsProto(valid_media_info1_)))
|
||||||
.WillOnce(Return(representation1.get()));
|
.WillOnce(Return(representation1.get()));
|
||||||
// The second AdaptationSet should have Ger MP4, one Representation.
|
// Return the same adaptation set for |valid_media_info2_| and
|
||||||
EXPECT_CALL(*adaptation_set2, AddRepresentation(_))
|
// |valid_media_info3_|. This results in AddRepresentation to be called twice
|
||||||
.WillOnce(Return(representation2.get()));
|
// on |adaptation_set2|.
|
||||||
// The third AdaptationSet should have Ger WebM, two Representations.
|
EXPECT_CALL(*default_mock_period_,
|
||||||
EXPECT_CALL(*adaptation_set3, AddRepresentation(_))
|
GetOrCreateAdaptationSet(AnyOf(EqualsProto(valid_media_info2_),
|
||||||
.WillOnce(Return(representation3.get()))
|
EqualsProto(valid_media_info3_)),
|
||||||
.WillOnce(Return(representation4.get()));
|
_))
|
||||||
|
.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;
|
uint32_t unused_container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(
|
||||||
ConvertToMediaInfo(kAudioContent1), &unused_container_id));
|
notifier.NotifyNewContainer(valid_media_info1_, &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(
|
||||||
ConvertToMediaInfo(kAudioContent2), &unused_container_id));
|
notifier.NotifyNewContainer(valid_media_info2_, &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(
|
||||||
ConvertToMediaInfo(kAudioContent3), &unused_container_id));
|
notifier.NotifyNewContainer(valid_media_info3_, &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
|
||||||
ConvertToMediaInfo(kAudioContent4), &unused_container_id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -13,6 +13,9 @@ const MpdOptions kDefaultMpdOptions;
|
||||||
MockMpdBuilder::MockMpdBuilder() : MpdBuilder(kDefaultMpdOptions) {}
|
MockMpdBuilder::MockMpdBuilder() : MpdBuilder(kDefaultMpdOptions) {}
|
||||||
MockMpdBuilder::~MockMpdBuilder() {}
|
MockMpdBuilder::~MockMpdBuilder() {}
|
||||||
|
|
||||||
|
MockPeriod::MockPeriod()
|
||||||
|
: Period(kDefaultMpdOptions, &sequence_counter_, &sequence_counter_) {}
|
||||||
|
|
||||||
MockAdaptationSet::MockAdaptationSet(uint32_t adaptation_set_id)
|
MockAdaptationSet::MockAdaptationSet(uint32_t adaptation_set_id)
|
||||||
: AdaptationSet(adaptation_set_id,
|
: AdaptationSet(adaptation_set_id,
|
||||||
kEmptyLang,
|
kEmptyLang,
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "packager/mpd/base/adaptation_set.h"
|
#include "packager/mpd/base/adaptation_set.h"
|
||||||
#include "packager/mpd/base/content_protection_element.h"
|
#include "packager/mpd/base/content_protection_element.h"
|
||||||
#include "packager/mpd/base/mpd_builder.h"
|
#include "packager/mpd/base/mpd_builder.h"
|
||||||
|
#include "packager/mpd/base/period.h"
|
||||||
#include "packager/mpd/base/representation.h"
|
#include "packager/mpd/base/representation.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
@ -23,10 +24,23 @@ class MockMpdBuilder : public MpdBuilder {
|
||||||
MockMpdBuilder();
|
MockMpdBuilder();
|
||||||
~MockMpdBuilder() override;
|
~MockMpdBuilder() override;
|
||||||
|
|
||||||
MOCK_METHOD1(AddAdaptationSet, AdaptationSet*(const std::string& lang));
|
MOCK_METHOD0(AddPeriod, Period*());
|
||||||
MOCK_METHOD1(ToString, bool(std::string* output));
|
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 {
|
class MockAdaptationSet : public AdaptationSet {
|
||||||
public:
|
public:
|
||||||
// |adaptation_set_id| is the id for the AdaptationSet.
|
// |adaptation_set_id| is the id for the AdaptationSet.
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "packager/base/time/time.h"
|
#include "packager/base/time/time.h"
|
||||||
#include "packager/mpd/base/adaptation_set.h"
|
#include "packager/mpd/base/adaptation_set.h"
|
||||||
#include "packager/mpd/base/mpd_utils.h"
|
#include "packager/mpd/base/mpd_utils.h"
|
||||||
|
#include "packager/mpd/base/period.h"
|
||||||
#include "packager/mpd/base/xml/xml_node.h"
|
#include "packager/mpd/base/xml/xml_node.h"
|
||||||
#include "packager/version/version.h"
|
#include "packager/version/version.h"
|
||||||
|
|
||||||
|
@ -139,18 +140,10 @@ void MpdBuilder::AddBaseUrl(const std::string& base_url) {
|
||||||
base_urls_.push_back(base_url);
|
base_urls_.push_back(base_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdaptationSet* MpdBuilder::AddAdaptationSet(const std::string& lang) {
|
Period* MpdBuilder::AddPeriod() {
|
||||||
std::unique_ptr<AdaptationSet> adaptation_set(
|
periods_.emplace_back(new Period(mpd_options_, &adaptation_set_counter_,
|
||||||
new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
|
|
||||||
&representation_counter_));
|
&representation_counter_));
|
||||||
DCHECK(adaptation_set);
|
return periods_.back().get();
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MpdBuilder::ToString(std::string* output) {
|
bool MpdBuilder::ToString(std::string* output) {
|
||||||
|
@ -158,7 +151,7 @@ bool MpdBuilder::ToString(std::string* output) {
|
||||||
static LibXmlInitializer lib_xml_initializer;
|
static LibXmlInitializer lib_xml_initializer;
|
||||||
|
|
||||||
xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
|
xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
|
||||||
if (!doc.get())
|
if (!doc)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
static const int kNiceFormat = 1;
|
static const int kNiceFormat = 1;
|
||||||
|
@ -180,39 +173,21 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
|
||||||
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
|
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
|
||||||
XmlNode mpd("MPD");
|
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.
|
// Add baseurls to MPD.
|
||||||
std::list<std::string>::const_iterator base_urls_it = base_urls_.begin();
|
for (const std::string& base_url : base_urls_) {
|
||||||
for (; base_urls_it != base_urls_.end(); ++base_urls_it) {
|
XmlNode xml_base_url("BaseURL");
|
||||||
XmlNode base_url("BaseURL");
|
xml_base_url.SetContent(base_url);
|
||||||
base_url.SetContent(*base_urls_it);
|
|
||||||
|
|
||||||
if (!mpd.AddChild(base_url.PassScopedPtr()))
|
if (!mpd.AddChild(xml_base_url.PassScopedPtr()))
|
||||||
return NULL;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kqyang): Should we set @start unconditionally to 0?
|
for (const auto& period : periods_) {
|
||||||
if (mpd_options_.mpd_type == MpdType::kDynamic) {
|
xml::scoped_xml_ptr<xmlNode> period_node(period->GetXml());
|
||||||
// This is the only Period and it is a regular period.
|
if (!period_node || !mpd.AddChild(std::move(period_node)))
|
||||||
period.SetStringAttribute("start", "PT0S");
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mpd.AddChild(period.PassScopedPtr()))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
AddMpdNameSpaceInfo(&mpd);
|
AddMpdNameSpaceInfo(&mpd);
|
||||||
|
|
||||||
static const char kOnDemandProfile[] =
|
static const char kOnDemandProfile[] =
|
||||||
|
@ -331,15 +306,21 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
|
||||||
DCHECK(mpd_node);
|
DCHECK(mpd_node);
|
||||||
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
|
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
|
// Attribute mediaPresentationDuration must be present for 'static' MPD. So
|
||||||
// setting "PT0S" is required even if none of the representaions have duration
|
// setting "PT0S" is required even if none of the representaions have duration
|
||||||
// attribute.
|
// attribute.
|
||||||
float max_duration = 0.0f;
|
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);
|
for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
|
||||||
adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
|
adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
|
||||||
for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
|
for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
|
||||||
|
@ -361,21 +342,8 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
|
||||||
|
|
||||||
bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
|
bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
|
||||||
DCHECK(timestamp_seconds);
|
DCHECK(timestamp_seconds);
|
||||||
|
DCHECK(!periods_.empty());
|
||||||
double earliest_timestamp(-1);
|
return periods_.front()->GetEarliestTimestamp(timestamp_seconds);
|
||||||
for (const std::unique_ptr<AdaptationSet>& adaptation_set :
|
|
||||||
adaptation_sets_) {
|
|
||||||
double timestamp;
|
|
||||||
if (adaptation_set->GetEarliestTimestamp(×tamp) &&
|
|
||||||
((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
|
|
||||||
earliest_timestamp = timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (earliest_timestamp < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
*timestamp_seconds = earliest_timestamp;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
|
void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
|
||||||
|
|
|
@ -28,6 +28,7 @@ namespace shaka {
|
||||||
|
|
||||||
class AdaptationSet;
|
class AdaptationSet;
|
||||||
class MediaInfo;
|
class MediaInfo;
|
||||||
|
class Period;
|
||||||
|
|
||||||
namespace xml {
|
namespace xml {
|
||||||
class XmlNode;
|
class XmlNode;
|
||||||
|
@ -45,11 +46,9 @@ class MpdBuilder {
|
||||||
/// @param base_url URL for <BaseURL> entry.
|
/// @param base_url URL for <BaseURL> entry.
|
||||||
void AddBaseUrl(const std::string& base_url);
|
void AddBaseUrl(const std::string& base_url);
|
||||||
|
|
||||||
/// Adds <AdaptationSet> to the MPD.
|
/// Adds <Period> to the MPD.
|
||||||
/// @param lang is the language of the AdaptationSet. This can be empty for
|
/// @return The new period, which is owned by this instance.
|
||||||
/// videos, for example.
|
virtual Period* AddPeriod();
|
||||||
/// @return The new adaptation set, which is owned by this instance.
|
|
||||||
virtual AdaptationSet* AddAdaptationSet(const std::string& lang);
|
|
||||||
|
|
||||||
/// Writes the MPD to the given string.
|
/// Writes the MPD to the given string.
|
||||||
/// @param[out] output is an output string where the MPD gets written.
|
/// @param[out] output is an output string where the MPD gets written.
|
||||||
|
@ -108,7 +107,7 @@ class MpdBuilder {
|
||||||
bool GetEarliestTimestamp(double* timestamp_seconds);
|
bool GetEarliestTimestamp(double* timestamp_seconds);
|
||||||
|
|
||||||
MpdOptions mpd_options_;
|
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::list<std::string> base_urls_;
|
||||||
std::string availability_start_time_;
|
std::string availability_start_time_;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include "packager/mpd/base/adaptation_set.h"
|
#include "packager/mpd/base/adaptation_set.h"
|
||||||
#include "packager/mpd/base/mpd_builder.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/mpd/test/mpd_builder_test_helper.h"
|
||||||
#include "packager/version/version.h"
|
#include "packager/version/version.h"
|
||||||
|
|
||||||
|
@ -35,6 +36,11 @@ class MpdBuilderTest : public ::testing::Test {
|
||||||
}
|
}
|
||||||
~MpdBuilderTest() override {}
|
~MpdBuilderTest() override {}
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
period_ = mpd_.AddPeriod();
|
||||||
|
ASSERT_TRUE(period_);
|
||||||
|
}
|
||||||
|
|
||||||
MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; }
|
MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; }
|
||||||
|
|
||||||
void CheckMpd(const std::string& expected_output_file) {
|
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
|
// Creates a new AdaptationSet and adds a Representation element using
|
||||||
// |media_info|.
|
// |media_info|.
|
||||||
void AddRepresentation(const MediaInfo& 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);
|
ASSERT_TRUE(adaptation_set);
|
||||||
|
|
||||||
Representation* representation =
|
Representation* representation =
|
||||||
|
@ -66,6 +73,7 @@ class MpdBuilderTest : public ::testing::Test {
|
||||||
Representation* representation_; // Owned by |mpd_|.
|
Representation* representation_; // Owned by |mpd_|.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Period* period_ = nullptr;
|
||||||
base::AtomicSequenceNumber representation_counter_;
|
base::AtomicSequenceNumber representation_counter_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(MpdBuilderTest);
|
DISALLOW_COPY_AND_ASSIGN(MpdBuilderTest);
|
||||||
|
@ -110,13 +118,11 @@ TEST_F(OnDemandMpdBuilderTest, Video) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(OnDemandMpdBuilderTest, TwoVideosWithDifferentResolutions) {
|
TEST_F(OnDemandMpdBuilderTest, TwoVideosWithDifferentResolutions) {
|
||||||
AdaptationSet* adaptation_set = mpd_.AddAdaptationSet("");
|
|
||||||
|
|
||||||
MediaInfo media_info1 = GetTestMediaInfo(kFileNameVideoMediaInfo1);
|
MediaInfo media_info1 = GetTestMediaInfo(kFileNameVideoMediaInfo1);
|
||||||
ASSERT_TRUE(adaptation_set->AddRepresentation(media_info1));
|
|
||||||
|
|
||||||
MediaInfo media_info2 = GetTestMediaInfo(kFileNameVideoMediaInfo2);
|
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));
|
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputVideo1And2));
|
||||||
}
|
}
|
||||||
|
@ -125,21 +131,9 @@ TEST_F(OnDemandMpdBuilderTest, TwoVideosWithDifferentResolutions) {
|
||||||
TEST_F(OnDemandMpdBuilderTest, VideoAndAudio) {
|
TEST_F(OnDemandMpdBuilderTest, VideoAndAudio) {
|
||||||
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
|
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
|
||||||
MediaInfo audio_media_info = GetTestMediaInfo(kFileNameAudioMediaInfo1);
|
MediaInfo audio_media_info = GetTestMediaInfo(kFileNameAudioMediaInfo1);
|
||||||
|
|
||||||
// The order matters here to check against expected output.
|
// The order matters here to check against expected output.
|
||||||
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
|
ASSERT_NO_FATAL_FAILURE(AddRepresentation(video_media_info));
|
||||||
ASSERT_TRUE(video_adaptation_set);
|
ASSERT_NO_FATAL_FAILURE(AddRepresentation(audio_media_info));
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputAudio1AndVideo1));
|
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputAudio1AndVideo1));
|
||||||
}
|
}
|
||||||
|
@ -166,19 +160,18 @@ TEST_F(LiveMpdBuilderTest, DynamicCheckMpdAttributes) {
|
||||||
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
|
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
|
||||||
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
|
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
|
||||||
" xmlns:xlink=\"http://www.w3.org/1999/xlink\""
|
" xmlns:xlink=\"http://www.w3.org/1999/xlink\""
|
||||||
"xsi:schemaLocation="
|
" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\""
|
||||||
"\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\" "
|
|
||||||
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
|
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
|
||||||
" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\""
|
" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\""
|
||||||
" minBufferTime=\"PT2S\""
|
" minBufferTime=\"PT2S\""
|
||||||
" type=\"dynamic\""
|
" type=\"dynamic\""
|
||||||
" publishTime=\"2016-01-11T15:10:24Z\""
|
" publishTime=\"2016-01-11T15:10:24Z\""
|
||||||
"availabilityStartTime=\"2011-12-25T12:30:00\">\n"
|
" availabilityStartTime=\"2011-12-25T12:30:00\""
|
||||||
" <Period id=\"0\" start=\"PT0S\"/>\n"
|
" minimumUpdatePeriod=\"PT2S\"/>\n";
|
||||||
"</MPD>\n";
|
|
||||||
|
|
||||||
std::string mpd_doc;
|
std::string mpd_doc;
|
||||||
mutable_mpd_options()->mpd_type = MpdType::kDynamic;
|
mutable_mpd_options()->mpd_type = MpdType::kDynamic;
|
||||||
|
mutable_mpd_options()->mpd_params.minimum_update_period = 2;
|
||||||
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
|
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
|
||||||
ASSERT_EQ(kExpectedOutput, mpd_doc);
|
ASSERT_EQ(kExpectedOutput, mpd_doc);
|
||||||
}
|
}
|
||||||
|
@ -191,15 +184,12 @@ TEST_F(LiveMpdBuilderTest, StaticCheckMpdAttributes) {
|
||||||
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
|
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
|
||||||
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
|
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
|
||||||
" xmlns:xlink=\"http://www.w3.org/1999/xlink\""
|
" xmlns:xlink=\"http://www.w3.org/1999/xlink\""
|
||||||
"xsi:schemaLocation="
|
" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\""
|
||||||
"\"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd\" "
|
|
||||||
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
|
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
|
||||||
" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\""
|
" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\""
|
||||||
" minBufferTime=\"PT2S\""
|
" minBufferTime=\"PT2S\""
|
||||||
" type=\"static\""
|
" type=\"static\""
|
||||||
"mediaPresentationDuration=\"PT0S\">\n"
|
" mediaPresentationDuration=\"PT0S\"/>\n";
|
||||||
" <Period id=\"0\"/>\n"
|
|
||||||
"</MPD>\n";
|
|
||||||
|
|
||||||
std::string mpd_doc;
|
std::string mpd_doc;
|
||||||
mutable_mpd_options()->mpd_type = MpdType::kStatic;
|
mutable_mpd_options()->mpd_type = MpdType::kStatic;
|
||||||
|
|
|
@ -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(×tamp) &&
|
||||||
|
(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
|
|
@ -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_
|
|
@ -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
|
|
@ -7,6 +7,7 @@
|
||||||
#include "packager/mpd/base/representation.h"
|
#include "packager/mpd/base/representation.h"
|
||||||
|
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
|
#include "packager/mpd/base/mpd_options.h"
|
||||||
#include "packager/mpd/base/mpd_utils.h"
|
#include "packager/mpd/base/mpd_utils.h"
|
||||||
#include "packager/mpd/base/xml/xml_node.h"
|
#include "packager/mpd/base/xml/xml_node.h"
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
//
|
//
|
||||||
/// All the methods that are virtual are virtual for mocking.
|
/// 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/bandwidth_estimator.h"
|
||||||
#include "packager/mpd/base/media_info.pb.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/segment_info.h"
|
||||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||||
|
|
||||||
|
@ -20,6 +22,7 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
|
||||||
struct ContentProtectionElement;
|
struct ContentProtectionElement;
|
||||||
|
struct MpdOptions;
|
||||||
|
|
||||||
namespace xml {
|
namespace xml {
|
||||||
class XmlNode;
|
class XmlNode;
|
||||||
|
@ -198,3 +201,5 @@ class Representation {
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
||||||
|
#endif // PACKAGER_MPD_BASE_REPRESENTATION_H_
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
|
||||||
#include "packager/base/strings/stringprintf.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/mpd_builder_test_helper.h"
|
||||||
#include "packager/mpd/test/xml_compare.h"
|
#include "packager/mpd/test/xml_compare.h"
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,13 @@
|
||||||
#include "packager/mpd/base/mpd_builder.h"
|
#include "packager/mpd/base/mpd_builder.h"
|
||||||
#include "packager/mpd/base/mpd_notifier_util.h"
|
#include "packager/mpd/base/mpd_notifier_util.h"
|
||||||
#include "packager/mpd/base/mpd_utils.h"
|
#include "packager/mpd/base/mpd_utils.h"
|
||||||
|
#include "packager/mpd/base/period.h"
|
||||||
#include "packager/mpd/base/representation.h"
|
#include "packager/mpd/base/representation.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const bool kContentProtectionInAdaptationSet = true;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
|
||||||
SimpleMpdNotifier::SimpleMpdNotifier(const MpdOptions& mpd_options)
|
SimpleMpdNotifier::SimpleMpdNotifier(const MpdOptions& mpd_options)
|
||||||
|
@ -40,20 +45,16 @@ bool SimpleMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
base::AutoLock auto_lock(lock_);
|
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);
|
MediaInfo adjusted_media_info(media_info);
|
||||||
MpdBuilder::MakePathsRelativeToMpd(output_path_, &adjusted_media_info);
|
MpdBuilder::MakePathsRelativeToMpd(output_path_, &adjusted_media_info);
|
||||||
Representation* representation =
|
Representation* representation =
|
||||||
(*adaptation_set)->AddRepresentation(adjusted_media_info);
|
adaptation_set->AddRepresentation(adjusted_media_info);
|
||||||
if (representation == NULL)
|
if (representation == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace shaka {
|
||||||
|
|
||||||
class AdaptationSet;
|
class AdaptationSet;
|
||||||
class MpdBuilder;
|
class MpdBuilder;
|
||||||
|
class Period;
|
||||||
class Representation;
|
class Representation;
|
||||||
class SimpleMpdNotifierTest;
|
class SimpleMpdNotifierTest;
|
||||||
|
|
||||||
|
@ -69,6 +70,7 @@ class SimpleMpdNotifier : public MpdNotifier {
|
||||||
// MPD output path.
|
// MPD output path.
|
||||||
std::string output_path_;
|
std::string output_path_;
|
||||||
std::unique_ptr<MpdBuilder> mpd_builder_;
|
std::unique_ptr<MpdBuilder> mpd_builder_;
|
||||||
|
Period* period_ = nullptr;
|
||||||
base::Lock lock_;
|
base::Lock lock_;
|
||||||
|
|
||||||
typedef std::map<std::string, AdaptationSet*> AdaptationSetMap;
|
typedef std::map<std::string, AdaptationSet*> AdaptationSetMap;
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <google/protobuf/util/message_differencer.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/base/files/file_path.h"
|
#include "packager/base/files/file_path.h"
|
||||||
|
@ -17,10 +19,32 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
|
using ::testing::Eq;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
using ::testing::StrEq;
|
using ::testing::StrEq;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
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_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[] =
|
const char kValidMediaInfo[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
" codec: 'avc1'\n"
|
" codec: 'avc1'\n"
|
||||||
|
@ -32,18 +56,11 @@ const char kValidMediaInfo[] =
|
||||||
" pixel_height: 1\n"
|
" pixel_height: 1\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"container_type: 1\n";
|
"container_type: 1\n";
|
||||||
const uint32_t kDefaultAdaptationSetId = 0u;
|
valid_media_info1_ = ConvertToMediaInfo(kValidMediaInfo);
|
||||||
} // namespace
|
valid_media_info2_ = valid_media_info1_;
|
||||||
|
valid_media_info2_.mutable_video_info()->set_width(960);
|
||||||
class SimpleMpdNotifierTest : public ::testing::Test {
|
valid_media_info3_ = valid_media_info1_;
|
||||||
protected:
|
valid_media_info3_.mutable_video_info()->set_width(480);
|
||||||
SimpleMpdNotifierTest()
|
|
||||||
: 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TearDown() override {
|
void TearDown() override {
|
||||||
|
@ -55,14 +72,21 @@ class SimpleMpdNotifierTest : public ::testing::Test {
|
||||||
notifier->SetMpdBuilderForTesting(std::move(mpd_builder));
|
notifier->SetMpdBuilderForTesting(std::move(mpd_builder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
// Empty mpd options except with output path specified, so that
|
// Empty mpd options except with output path specified, so that
|
||||||
// WriteMpdToFile() doesn't crash.
|
// WriteMpdToFile() doesn't crash.
|
||||||
MpdOptions empty_mpd_option_;
|
MpdOptions empty_mpd_option_;
|
||||||
const std::vector<std::string> empty_base_urls_;
|
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_;
|
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:
|
private:
|
||||||
base::FilePath temp_file_path_;
|
base::FilePath temp_file_path_;
|
||||||
};
|
};
|
||||||
|
@ -76,9 +100,14 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewContainer) {
|
||||||
std::unique_ptr<MockRepresentation> mock_representation(
|
std::unique_ptr<MockRepresentation> mock_representation(
|
||||||
new MockRepresentation(kRepresentationId));
|
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()));
|
.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()));
|
.WillOnce(Return(mock_representation.get()));
|
||||||
|
|
||||||
// This is for the Flush() below but adding expectation here because the next
|
// 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;
|
uint32_t unused_container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
EXPECT_TRUE(
|
||||||
&unused_container_id));
|
notifier.NotifyNewContainer(valid_media_info1_, &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.Flush());
|
EXPECT_TRUE(notifier.Flush());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,15 +129,16 @@ TEST_F(SimpleMpdNotifierTest, NotifySampleDuration) {
|
||||||
std::unique_ptr<MockRepresentation> mock_representation(
|
std::unique_ptr<MockRepresentation> mock_representation(
|
||||||
new MockRepresentation(kRepresentationId));
|
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()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||||
.WillOnce(Return(mock_representation.get()));
|
.WillOnce(Return(mock_representation.get()));
|
||||||
|
|
||||||
uint32_t container_id;
|
uint32_t container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
&container_id));
|
|
||||||
EXPECT_EQ(kRepresentationId, container_id);
|
EXPECT_EQ(kRepresentationId, container_id);
|
||||||
|
|
||||||
const uint32_t kSampleDuration = 100;
|
const uint32_t kSampleDuration = 100;
|
||||||
|
@ -125,8 +155,7 @@ TEST_F(SimpleMpdNotifierTest, NotifySampleDuration) {
|
||||||
TEST_F(SimpleMpdNotifierTest, NotifyNewContainerAndSampleDurationNoMock) {
|
TEST_F(SimpleMpdNotifierTest, NotifyNewContainerAndSampleDurationNoMock) {
|
||||||
SimpleMpdNotifier notifier(empty_mpd_option_);
|
SimpleMpdNotifier notifier(empty_mpd_option_);
|
||||||
uint32_t container_id;
|
uint32_t container_id;
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
&container_id));
|
|
||||||
const uint32_t kAnySampleDuration = 1000;
|
const uint32_t kAnySampleDuration = 1000;
|
||||||
EXPECT_TRUE(notifier.NotifySampleDuration(container_id, kAnySampleDuration));
|
EXPECT_TRUE(notifier.NotifySampleDuration(container_id, kAnySampleDuration));
|
||||||
EXPECT_TRUE(notifier.Flush());
|
EXPECT_TRUE(notifier.Flush());
|
||||||
|
@ -140,15 +169,16 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewSegment) {
|
||||||
std::unique_ptr<MockRepresentation> mock_representation(
|
std::unique_ptr<MockRepresentation> mock_representation(
|
||||||
new MockRepresentation(kRepresentationId));
|
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()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||||
.WillOnce(Return(mock_representation.get()));
|
.WillOnce(Return(mock_representation.get()));
|
||||||
|
|
||||||
uint32_t container_id;
|
uint32_t container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
&container_id));
|
|
||||||
EXPECT_EQ(kRepresentationId, container_id);
|
EXPECT_EQ(kRepresentationId, container_id);
|
||||||
|
|
||||||
const uint64_t kStartTime = 0u;
|
const uint64_t kStartTime = 0u;
|
||||||
|
@ -169,15 +199,16 @@ TEST_F(SimpleMpdNotifierTest, AddContentProtectionElement) {
|
||||||
std::unique_ptr<MockRepresentation> mock_representation(
|
std::unique_ptr<MockRepresentation> mock_representation(
|
||||||
new MockRepresentation(kRepresentationId));
|
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()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||||
.WillOnce(Return(mock_representation.get()));
|
.WillOnce(Return(mock_representation.get()));
|
||||||
|
|
||||||
uint32_t container_id;
|
uint32_t container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kValidMediaInfo),
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
&container_id));
|
|
||||||
EXPECT_EQ(kRepresentationId, container_id);
|
EXPECT_EQ(kRepresentationId, container_id);
|
||||||
|
|
||||||
ContentProtectionElement element;
|
ContentProtectionElement element;
|
||||||
|
@ -186,40 +217,22 @@ TEST_F(SimpleMpdNotifierTest, AddContentProtectionElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SimpleMpdNotifierTest, UpdateEncryption) {
|
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_);
|
SimpleMpdNotifier notifier(empty_mpd_option_);
|
||||||
const uint32_t kRepresentationId = 447834u;
|
const uint32_t kRepresentationId = 447834u;
|
||||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
||||||
std::unique_ptr<MockRepresentation> mock_representation(
|
std::unique_ptr<MockRepresentation> mock_representation(
|
||||||
new MockRepresentation(kRepresentationId));
|
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()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||||
.WillOnce(Return(mock_representation.get()));
|
.WillOnce(Return(mock_representation.get()));
|
||||||
|
|
||||||
uint32_t container_id;
|
uint32_t container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kProtectedContent),
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
&container_id));
|
|
||||||
|
|
||||||
::testing::Mock::VerifyAndClearExpectations(
|
::testing::Mock::VerifyAndClearExpectations(
|
||||||
default_mock_adaptation_set_.get());
|
default_mock_adaptation_set_.get());
|
||||||
|
@ -239,66 +252,13 @@ TEST_F(SimpleMpdNotifierTest, UpdateEncryption) {
|
||||||
container_id, "myuuid", std::vector<uint8_t>(), kBogusNewPsshVector));
|
container_id, "myuuid", std::vector<uint8_t>(), kBogusNewPsshVector));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't put different audio languages or codecs in the same AdaptationSet.
|
// Test multiple media info with some belongs to the same AdaptationSets.
|
||||||
TEST_F(SimpleMpdNotifierTest, SplitAdaptationSetsByLanguageAndCodec) {
|
TEST_F(SimpleMpdNotifierTest, MultipleMediaInfo) {
|
||||||
// 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";
|
|
||||||
|
|
||||||
SimpleMpdNotifier notifier(empty_mpd_option_);
|
SimpleMpdNotifier notifier(empty_mpd_option_);
|
||||||
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
||||||
|
|
||||||
std::unique_ptr<MockAdaptationSet> adaptation_set1(new MockAdaptationSet(1));
|
std::unique_ptr<MockAdaptationSet> adaptation_set1(new MockAdaptationSet(1));
|
||||||
std::unique_ptr<MockAdaptationSet> adaptation_set2(new MockAdaptationSet(2));
|
std::unique_ptr<MockAdaptationSet> adaptation_set2(new MockAdaptationSet(2));
|
||||||
std::unique_ptr<MockAdaptationSet> adaptation_set3(new MockAdaptationSet(3));
|
|
||||||
|
|
||||||
std::unique_ptr<MockRepresentation> representation1(
|
std::unique_ptr<MockRepresentation> representation1(
|
||||||
new MockRepresentation(1));
|
new MockRepresentation(1));
|
||||||
|
@ -306,35 +266,40 @@ TEST_F(SimpleMpdNotifierTest, SplitAdaptationSetsByLanguageAndCodec) {
|
||||||
new MockRepresentation(2));
|
new MockRepresentation(2));
|
||||||
std::unique_ptr<MockRepresentation> representation3(
|
std::unique_ptr<MockRepresentation> representation3(
|
||||||
new MockRepresentation(3));
|
new MockRepresentation(3));
|
||||||
std::unique_ptr<MockRepresentation> representation4(
|
|
||||||
new MockRepresentation(4));
|
|
||||||
|
|
||||||
// We expect three AdaptationSets.
|
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
|
||||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_))
|
.WillOnce(Return(default_mock_period_.get()));
|
||||||
.WillOnce(Return(adaptation_set1.get()))
|
|
||||||
.WillOnce(Return(adaptation_set2.get()))
|
EXPECT_CALL(*default_mock_period_,
|
||||||
.WillOnce(Return(adaptation_set3.get()));
|
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
|
||||||
// The first AdaptationSet should have Eng MP4, one Representation.
|
.WillOnce(Return(adaptation_set1.get()));
|
||||||
EXPECT_CALL(*adaptation_set1, AddRepresentation(_))
|
EXPECT_CALL(*adaptation_set1,
|
||||||
|
AddRepresentation(EqualsProto(valid_media_info1_)))
|
||||||
.WillOnce(Return(representation1.get()));
|
.WillOnce(Return(representation1.get()));
|
||||||
// The second AdaptationSet should have Ger MP4, one Representation.
|
// Return the same adaptation set for |valid_media_info2_| and
|
||||||
EXPECT_CALL(*adaptation_set2, AddRepresentation(_))
|
// |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()));
|
.WillOnce(Return(representation2.get()));
|
||||||
// The third AdaptationSet should have Ger WebM, two Representations.
|
EXPECT_CALL(*default_mock_period_,
|
||||||
EXPECT_CALL(*adaptation_set3, AddRepresentation(_))
|
GetOrCreateAdaptationSet(EqualsProto(valid_media_info3_), _))
|
||||||
.WillOnce(Return(representation3.get()))
|
.WillOnce(Return(adaptation_set2.get()));
|
||||||
.WillOnce(Return(representation4.get()));
|
EXPECT_CALL(*adaptation_set2,
|
||||||
|
AddRepresentation(EqualsProto(valid_media_info3_)))
|
||||||
|
.WillOnce(Return(representation3.get()));
|
||||||
|
|
||||||
uint32_t unused_container_id;
|
uint32_t unused_container_id;
|
||||||
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(
|
||||||
ConvertToMediaInfo(kAudioContent1), &unused_container_id));
|
notifier.NotifyNewContainer(valid_media_info1_, &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(
|
||||||
ConvertToMediaInfo(kAudioContent2), &unused_container_id));
|
notifier.NotifyNewContainer(valid_media_info2_, &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
EXPECT_TRUE(
|
||||||
ConvertToMediaInfo(kAudioContent3), &unused_container_id));
|
notifier.NotifyNewContainer(valid_media_info3_, &unused_container_id));
|
||||||
EXPECT_TRUE(notifier.NotifyNewContainer(
|
|
||||||
ConvertToMediaInfo(kAudioContent4), &unused_container_id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -43,6 +43,8 @@
|
||||||
'base/mpd_options.h',
|
'base/mpd_options.h',
|
||||||
'base/mpd_utils.cc',
|
'base/mpd_utils.cc',
|
||||||
'base/mpd_utils.h',
|
'base/mpd_utils.h',
|
||||||
|
'base/period.cc',
|
||||||
|
'base/period.h',
|
||||||
'base/representation.cc',
|
'base/representation.cc',
|
||||||
'base/representation.h',
|
'base/representation.h',
|
||||||
'base/segment_info.h',
|
'base/segment_info.h',
|
||||||
|
@ -88,6 +90,7 @@
|
||||||
'base/bandwidth_estimator_unittest.cc',
|
'base/bandwidth_estimator_unittest.cc',
|
||||||
'base/dash_iop_mpd_notifier_unittest.cc',
|
'base/dash_iop_mpd_notifier_unittest.cc',
|
||||||
'base/mpd_builder_unittest.cc',
|
'base/mpd_builder_unittest.cc',
|
||||||
|
'base/period_unittest.cc',
|
||||||
'base/representation_unittest.cc',
|
'base/representation_unittest.cc',
|
||||||
'base/simple_mpd_notifier_unittest.cc',
|
'base/simple_mpd_notifier_unittest.cc',
|
||||||
'base/xml/xml_node_unittest.cc',
|
'base/xml/xml_node_unittest.cc',
|
||||||
|
|
|
@ -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">
|
<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">
|
<Period id="0">
|
||||||
<AdaptationSet id="0" width="720" height="480" frameRate="10/1" contentType="video" par="3:2">
|
<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>
|
<BaseURL>test_output_file_name1.mp4</BaseURL>
|
||||||
<SegmentBase indexRange="121-221" timescale="1000">
|
<SegmentBase indexRange="121-221" timescale="1000">
|
||||||
<Initialization range="0-120"/>
|
<Initialization range="0-120"/>
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet id="1" contentType="audio">
|
<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"/>
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
<BaseURL>test_output_file_name_audio1.mp4</BaseURL>
|
<BaseURL>test_output_file_name_audio1.mp4</BaseURL>
|
||||||
<SegmentBase timescale="50"/>
|
<SegmentBase timescale="50"/>
|
||||||
|
|
Loading…
Reference in New Issue