[DASH] Support multiple period
Change-Id: Ifd17bf0eabbd61ec7a1d35f0b864b5aa6666aa87
This commit is contained in:
parent
2ba23e1075
commit
d76ccea46f
|
@ -188,43 +188,30 @@ Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) {
|
||||||
// will die before AdaptationSet.
|
// will die before AdaptationSet.
|
||||||
std::unique_ptr<RepresentationStateChangeListener> listener(
|
std::unique_ptr<RepresentationStateChangeListener> listener(
|
||||||
new RepresentationStateChangeListenerImpl(representation_id, this));
|
new RepresentationStateChangeListenerImpl(representation_id, this));
|
||||||
std::unique_ptr<Representation> representation(new Representation(
|
std::unique_ptr<Representation> new_representation(new Representation(
|
||||||
media_info, mpd_options_, representation_id, std::move(listener)));
|
media_info, mpd_options_, representation_id, std::move(listener)));
|
||||||
|
|
||||||
if (!representation->Init()) {
|
if (!new_representation->Init()) {
|
||||||
LOG(ERROR) << "Failed to initialize Representation.";
|
LOG(ERROR) << "Failed to initialize Representation.";
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
UpdateFromMediaInfo(media_info);
|
||||||
|
representations_.push_back(std::move(new_representation));
|
||||||
|
return representations_.back().get();
|
||||||
|
}
|
||||||
|
|
||||||
// For videos, record the width, height, and the frame rate to calculate the
|
Representation* AdaptationSet::CopyRepresentationWithTimeOffset(
|
||||||
// max {width,height,framerate} required for DASH IOP.
|
const Representation& representation,
|
||||||
if (media_info.has_video_info()) {
|
uint64_t presentation_time_offset) {
|
||||||
const MediaInfo::VideoInfo& video_info = media_info.video_info();
|
// Note that AdaptationSet outlive Representation, so this object
|
||||||
DCHECK(video_info.has_width());
|
// will die before AdaptationSet.
|
||||||
DCHECK(video_info.has_height());
|
std::unique_ptr<RepresentationStateChangeListener> listener(
|
||||||
video_widths_.insert(video_info.width());
|
new RepresentationStateChangeListenerImpl(representation.id(), this));
|
||||||
video_heights_.insert(video_info.height());
|
std::unique_ptr<Representation> new_representation(new Representation(
|
||||||
|
representation, presentation_time_offset, std::move(listener)));
|
||||||
|
|
||||||
if (video_info.has_time_scale() && video_info.has_frame_duration())
|
UpdateFromMediaInfo(new_representation->GetMediaInfo());
|
||||||
RecordFrameRate(video_info.frame_duration(), video_info.time_scale());
|
representations_.push_back(std::move(new_representation));
|
||||||
|
|
||||||
AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (media_info.has_video_info()) {
|
|
||||||
content_type_ = "video";
|
|
||||||
} else if (media_info.has_audio_info()) {
|
|
||||||
content_type_ = "audio";
|
|
||||||
} else if (media_info.has_text_info()) {
|
|
||||||
content_type_ = "text";
|
|
||||||
|
|
||||||
if (media_info.text_info().has_type() &&
|
|
||||||
(media_info.text_info().type() != MediaInfo::TextInfo::UNKNOWN)) {
|
|
||||||
roles_.insert(MediaInfoTextTypeToRole(media_info.text_info().type()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
representations_.push_back(std::move(representation));
|
|
||||||
return representations_.back().get();
|
return representations_.back().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,6 +383,36 @@ const std::list<Representation*> AdaptationSet::GetRepresentations() const {
|
||||||
return representations;
|
return representations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AdaptationSet::UpdateFromMediaInfo(const MediaInfo& media_info) {
|
||||||
|
// For videos, record the width, height, and the frame rate to calculate the
|
||||||
|
// max {width,height,framerate} required for DASH IOP.
|
||||||
|
if (media_info.has_video_info()) {
|
||||||
|
const MediaInfo::VideoInfo& video_info = media_info.video_info();
|
||||||
|
DCHECK(video_info.has_width());
|
||||||
|
DCHECK(video_info.has_height());
|
||||||
|
video_widths_.insert(video_info.width());
|
||||||
|
video_heights_.insert(video_info.height());
|
||||||
|
|
||||||
|
if (video_info.has_time_scale() && video_info.has_frame_duration())
|
||||||
|
RecordFrameRate(video_info.frame_duration(), video_info.time_scale());
|
||||||
|
|
||||||
|
AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media_info.has_video_info()) {
|
||||||
|
content_type_ = "video";
|
||||||
|
} else if (media_info.has_audio_info()) {
|
||||||
|
content_type_ = "audio";
|
||||||
|
} else if (media_info.has_text_info()) {
|
||||||
|
content_type_ = "text";
|
||||||
|
|
||||||
|
if (media_info.text_info().has_type() &&
|
||||||
|
(media_info.text_info().type() != MediaInfo::TextInfo::UNKNOWN)) {
|
||||||
|
roles_.insert(MediaInfoTextTypeToRole(media_info.text_info().type()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This implementation assumes that each representations' segments' are
|
// This implementation assumes that each representations' segments' are
|
||||||
// contiguous.
|
// contiguous.
|
||||||
// Also assumes that all Representations are added before this is called.
|
// Also assumes that all Representations are added before this is called.
|
||||||
|
|
|
@ -59,6 +59,18 @@ class AdaptationSet {
|
||||||
/// NULL. The returned pointer is owned by the AdaptationSet instance.
|
/// NULL. The returned pointer is owned by the AdaptationSet instance.
|
||||||
virtual Representation* AddRepresentation(const MediaInfo& media_info);
|
virtual Representation* AddRepresentation(const MediaInfo& media_info);
|
||||||
|
|
||||||
|
/// Copy a Representation instance from @a representation in another
|
||||||
|
/// AdaptationSet. One use case is to duplicate Representation in different
|
||||||
|
/// periods.
|
||||||
|
/// @param representation is an existing Representation to be cloned from.
|
||||||
|
/// @param presentation_time_offset is the presentation time offset for the
|
||||||
|
/// new Representation instance.
|
||||||
|
/// @return On success, returns a pointer to Representation. Otherwise returns
|
||||||
|
/// NULL. The returned pointer is owned by the AdaptationSet instance.
|
||||||
|
virtual Representation* CopyRepresentationWithTimeOffset(
|
||||||
|
const Representation& representation,
|
||||||
|
uint64_t presentation_time_offset);
|
||||||
|
|
||||||
/// Add a ContenProtection element to the adaptation set.
|
/// Add a ContenProtection element to the adaptation set.
|
||||||
/// AdaptationSet does not add <ContentProtection> elements
|
/// AdaptationSet does not add <ContentProtection> elements
|
||||||
/// automatically to itself even if @a media_info.protected_content is
|
/// automatically to itself even if @a media_info.protected_content is
|
||||||
|
@ -111,6 +123,10 @@ class AdaptationSet {
|
||||||
// Must be unique in the Period.
|
// Must be unique in the Period.
|
||||||
uint32_t id() const { return id_; }
|
uint32_t id() const { return id_; }
|
||||||
|
|
||||||
|
/// Set AdaptationSet@id.
|
||||||
|
/// @param id is the new ID to be set.
|
||||||
|
void set_id(uint32_t id) { id_ = id; }
|
||||||
|
|
||||||
/// Notifies the AdaptationSet instance that a new (sub)segment was added to
|
/// Notifies the AdaptationSet instance that a new (sub)segment was added to
|
||||||
/// the Representation with @a representation_id.
|
/// the Representation with @a representation_id.
|
||||||
/// This must be called every time a (sub)segment is added to a
|
/// This must be called every time a (sub)segment is added to a
|
||||||
|
@ -191,6 +207,9 @@ class AdaptationSet {
|
||||||
// 2 -> [0, 200, 400]
|
// 2 -> [0, 200, 400]
|
||||||
typedef std::map<uint32_t, std::list<uint64_t>> RepresentationTimeline;
|
typedef std::map<uint32_t, std::list<uint64_t>> RepresentationTimeline;
|
||||||
|
|
||||||
|
// Update AdaptationSet attributes for new MediaInfo.
|
||||||
|
void UpdateFromMediaInfo(const MediaInfo& media_info);
|
||||||
|
|
||||||
/// Called from OnNewSegmentForRepresentation(). Checks whether the segments
|
/// Called from OnNewSegmentForRepresentation(). Checks whether the segments
|
||||||
/// are aligned. Sets segments_aligned_.
|
/// are aligned. Sets segments_aligned_.
|
||||||
/// This is only for Live. For VOD, CheckVodSegmentAlignment() should be used.
|
/// This is only for Live. For VOD, CheckVodSegmentAlignment() should be used.
|
||||||
|
@ -214,7 +233,7 @@ class AdaptationSet {
|
||||||
|
|
||||||
base::AtomicSequenceNumber* const representation_counter_;
|
base::AtomicSequenceNumber* const representation_counter_;
|
||||||
|
|
||||||
const uint32_t id_;
|
uint32_t id_;
|
||||||
const std::string lang_;
|
const std::string lang_;
|
||||||
const MpdOptions& mpd_options_;
|
const MpdOptions& mpd_options_;
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,31 @@ TEST_F(AdaptationSetTest, CheckAdaptationSetTextContentType) {
|
||||||
AttributeEqual("contentType", "text"));
|
AttributeEqual("contentType", "text"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(AdaptationSetTest, CopyRepresentationWithTimeOffset) {
|
||||||
|
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: CONTAINER_MP4\n";
|
||||||
|
|
||||||
|
auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage);
|
||||||
|
Representation* representation =
|
||||||
|
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo));
|
||||||
|
|
||||||
|
const uint64_t kPresentationTimeOffset = 80;
|
||||||
|
Representation* new_representation =
|
||||||
|
adaptation_set->CopyRepresentationWithTimeOffset(*representation,
|
||||||
|
kPresentationTimeOffset);
|
||||||
|
EXPECT_EQ(kPresentationTimeOffset,
|
||||||
|
new_representation->GetMediaInfo().presentation_time_offset());
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that language passed to the constructor sets the @lang field is set.
|
// Verify that language passed to the constructor sets the @lang field is set.
|
||||||
TEST_F(AdaptationSetTest, CheckLanguageAttributeSet) {
|
TEST_F(AdaptationSetTest, CheckLanguageAttributeSet) {
|
||||||
auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "en");
|
auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "en");
|
||||||
|
|
|
@ -133,6 +133,7 @@ message MediaInfo {
|
||||||
// This is the reference time scale if there are multiple VideoInfo and/or
|
// This is the reference time scale if there are multiple VideoInfo and/or
|
||||||
// AudioInfo.
|
// AudioInfo.
|
||||||
optional uint32 reference_time_scale = 13;
|
optional uint32 reference_time_scale = 13;
|
||||||
|
optional uint64 presentation_time_offset = 16;
|
||||||
optional ContainerType container_type = 14 [default = CONTAINER_UNKNOWN];
|
optional ContainerType container_type = 14 [default = CONTAINER_UNKNOWN];
|
||||||
|
|
||||||
// VOD only.
|
// VOD only.
|
||||||
|
|
|
@ -13,8 +13,12 @@ const MpdOptions kDefaultMpdOptions;
|
||||||
MockMpdBuilder::MockMpdBuilder() : MpdBuilder(kDefaultMpdOptions) {}
|
MockMpdBuilder::MockMpdBuilder() : MpdBuilder(kDefaultMpdOptions) {}
|
||||||
MockMpdBuilder::~MockMpdBuilder() {}
|
MockMpdBuilder::~MockMpdBuilder() {}
|
||||||
|
|
||||||
MockPeriod::MockPeriod()
|
MockPeriod::MockPeriod(uint32_t period_id, double start_time_in_seconds)
|
||||||
: Period(kDefaultMpdOptions, &sequence_counter_, &sequence_counter_) {}
|
: Period(period_id,
|
||||||
|
start_time_in_seconds,
|
||||||
|
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,
|
||||||
|
|
|
@ -24,13 +24,13 @@ class MockMpdBuilder : public MpdBuilder {
|
||||||
MockMpdBuilder();
|
MockMpdBuilder();
|
||||||
~MockMpdBuilder() override;
|
~MockMpdBuilder() override;
|
||||||
|
|
||||||
MOCK_METHOD0(AddPeriod, Period*());
|
MOCK_METHOD1(GetOrCreatePeriod, Period*(double start_time_in_seconds));
|
||||||
MOCK_METHOD1(ToString, bool(std::string* output));
|
MOCK_METHOD1(ToString, bool(std::string* output));
|
||||||
};
|
};
|
||||||
|
|
||||||
class MockPeriod : public Period {
|
class MockPeriod : public Period {
|
||||||
public:
|
public:
|
||||||
MockPeriod();
|
MockPeriod(uint32_t period_id, double start_time_in_seconds);
|
||||||
|
|
||||||
MOCK_METHOD2(GetOrCreateAdaptationSet,
|
MOCK_METHOD2(GetOrCreateAdaptationSet,
|
||||||
AdaptationSet*(const MediaInfo& media_info,
|
AdaptationSet*(const MediaInfo& media_info,
|
||||||
|
@ -48,6 +48,9 @@ class MockAdaptationSet : public AdaptationSet {
|
||||||
~MockAdaptationSet() override;
|
~MockAdaptationSet() override;
|
||||||
|
|
||||||
MOCK_METHOD1(AddRepresentation, Representation*(const MediaInfo& media_info));
|
MOCK_METHOD1(AddRepresentation, Representation*(const MediaInfo& media_info));
|
||||||
|
MOCK_METHOD2(CopyRepresentationWithTimeOffset,
|
||||||
|
Representation*(const Representation& representation,
|
||||||
|
uint64_t presentation_time_offset));
|
||||||
MOCK_METHOD1(AddContentProtectionElement,
|
MOCK_METHOD1(AddContentProtectionElement,
|
||||||
void(const ContentProtectionElement& element));
|
void(const ContentProtectionElement& element));
|
||||||
MOCK_METHOD2(UpdateContentProtectionPssh,
|
MOCK_METHOD2(UpdateContentProtectionPssh,
|
||||||
|
@ -75,6 +78,7 @@ class MockRepresentation : public Representation {
|
||||||
MOCK_METHOD3(AddNewSegment,
|
MOCK_METHOD3(AddNewSegment,
|
||||||
void(uint64_t start_time, uint64_t duration, uint64_t size));
|
void(uint64_t start_time, uint64_t duration, uint64_t size));
|
||||||
MOCK_METHOD1(SetSampleDuration, void(uint32_t sample_duration));
|
MOCK_METHOD1(SetSampleDuration, void(uint32_t sample_duration));
|
||||||
|
MOCK_CONST_METHOD0(GetMediaInfo, const MediaInfo&());
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -119,9 +119,18 @@ void MpdBuilder::AddBaseUrl(const std::string& base_url) {
|
||||||
base_urls_.push_back(base_url);
|
base_urls_.push_back(base_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Period* MpdBuilder::AddPeriod() {
|
Period* MpdBuilder::GetOrCreatePeriod(double start_time_in_seconds) {
|
||||||
periods_.emplace_back(new Period(mpd_options_, &adaptation_set_counter_,
|
for (auto& period : periods_) {
|
||||||
&representation_counter_));
|
const double kPeriodTimeDriftThresholdInSeconds = 1.0;
|
||||||
|
const bool match =
|
||||||
|
std::fabs(period->start_time_in_seconds() - start_time_in_seconds) <
|
||||||
|
kPeriodTimeDriftThresholdInSeconds;
|
||||||
|
if (match)
|
||||||
|
return period.get();
|
||||||
|
}
|
||||||
|
periods_.emplace_back(
|
||||||
|
new Period(period_counter_.GetNext(), start_time_in_seconds, mpd_options_,
|
||||||
|
&adaptation_set_counter_, &representation_counter_));
|
||||||
return periods_.back().get();
|
return periods_.back().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,9 +46,13 @@ 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 <Period> to the MPD.
|
/// Check the existing Periods, if there is one matching the provided
|
||||||
/// @return The new period, which is owned by this instance.
|
/// @a start_time_in_seconds, return it; otherwise a new Period is created and
|
||||||
virtual Period* AddPeriod();
|
/// returned.
|
||||||
|
/// @param start_time_in_seconds is the period start time.
|
||||||
|
/// @return the Period matching @a start_time_in_seconds if found; otherwise
|
||||||
|
/// return a new Period.
|
||||||
|
virtual Period* GetOrCreatePeriod(double start_time_in_seconds);
|
||||||
|
|
||||||
/// 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.
|
||||||
|
@ -112,6 +116,7 @@ class MpdBuilder {
|
||||||
std::list<std::string> base_urls_;
|
std::list<std::string> base_urls_;
|
||||||
std::string availability_start_time_;
|
std::string availability_start_time_;
|
||||||
|
|
||||||
|
base::AtomicSequenceNumber period_counter_;
|
||||||
base::AtomicSequenceNumber adaptation_set_counter_;
|
base::AtomicSequenceNumber adaptation_set_counter_;
|
||||||
base::AtomicSequenceNumber representation_counter_;
|
base::AtomicSequenceNumber representation_counter_;
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,8 @@ class MpdBuilderTest : public ::testing::Test {
|
||||||
~MpdBuilderTest() override {}
|
~MpdBuilderTest() override {}
|
||||||
|
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
period_ = mpd_.AddPeriod();
|
const double kPeriodStartTimeSeconds = 0.0;
|
||||||
|
period_ = mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds);
|
||||||
ASSERT_TRUE(period_);
|
ASSERT_TRUE(period_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +150,26 @@ TEST_F(OnDemandMpdBuilderTest, MediaInfoMissingBandwidth) {
|
||||||
ASSERT_FALSE(mpd_.ToString(&mpd_doc));
|
ASSERT_FALSE(mpd_.ToString(&mpd_doc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(LiveMpdBuilderTest, MultiplePeriodTest) {
|
||||||
|
const double kPeriodStartTimeSeconds = 1.0;
|
||||||
|
Period* period = mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds);
|
||||||
|
ASSERT_TRUE(period);
|
||||||
|
ASSERT_EQ(kPeriodStartTimeSeconds, period->start_time_in_seconds());
|
||||||
|
|
||||||
|
const double kPeriodStartTimeSeconds2 = 1.1;
|
||||||
|
Period* period2 = mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds2);
|
||||||
|
ASSERT_TRUE(period2);
|
||||||
|
// The old Period is re-used if they are closed to each other.
|
||||||
|
ASSERT_EQ(period, period2);
|
||||||
|
ASSERT_EQ(kPeriodStartTimeSeconds, period2->start_time_in_seconds());
|
||||||
|
|
||||||
|
const double kPeriodStartTimeSeconds3 = 5.0;
|
||||||
|
Period* period3 = mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds3);
|
||||||
|
ASSERT_TRUE(period3);
|
||||||
|
ASSERT_NE(period, period3);
|
||||||
|
ASSERT_EQ(kPeriodStartTimeSeconds3, period3->start_time_in_seconds());
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether the attributes are set correctly for dynamic <MPD> element.
|
// Check whether the attributes are set correctly for dynamic <MPD> element.
|
||||||
// This test must use ASSERT_EQ for comparison because XmlEqual() cannot
|
// This test must use ASSERT_EQ for comparison because XmlEqual() cannot
|
||||||
// handle namespaces correctly yet.
|
// handle namespaces correctly yet.
|
||||||
|
|
|
@ -34,10 +34,14 @@ std::set<std::string> GetUUIDs(
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Period::Period(const MpdOptions& mpd_options,
|
Period::Period(uint32_t period_id,
|
||||||
|
double start_time_in_seconds,
|
||||||
|
const MpdOptions& mpd_options,
|
||||||
base::AtomicSequenceNumber* adaptation_set_counter,
|
base::AtomicSequenceNumber* adaptation_set_counter,
|
||||||
base::AtomicSequenceNumber* representation_counter)
|
base::AtomicSequenceNumber* representation_counter)
|
||||||
: mpd_options_(mpd_options),
|
: id_(period_id),
|
||||||
|
start_time_in_seconds_(start_time_in_seconds),
|
||||||
|
mpd_options_(mpd_options),
|
||||||
adaptation_set_counter_(adaptation_set_counter),
|
adaptation_set_counter_(adaptation_set_counter),
|
||||||
representation_counter_(representation_counter) {}
|
representation_counter_(representation_counter) {}
|
||||||
|
|
||||||
|
@ -92,9 +96,8 @@ AdaptationSet* Period::GetOrCreateAdaptationSet(
|
||||||
xml::scoped_xml_ptr<xmlNode> Period::GetXml() {
|
xml::scoped_xml_ptr<xmlNode> Period::GetXml() {
|
||||||
xml::XmlNode period("Period");
|
xml::XmlNode period("Period");
|
||||||
|
|
||||||
// Always set id=0 for now.
|
|
||||||
// Required for 'dynamic' MPDs.
|
// Required for 'dynamic' MPDs.
|
||||||
period.SetId(0);
|
period.SetId(id_);
|
||||||
// Iterate thru AdaptationSets and add them to one big Period element.
|
// Iterate thru AdaptationSets and add them to one big Period element.
|
||||||
for (const auto& adaptation_set : adaptation_sets_) {
|
for (const auto& adaptation_set : adaptation_sets_) {
|
||||||
xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
|
xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
|
||||||
|
@ -102,10 +105,10 @@ xml::scoped_xml_ptr<xmlNode> Period::GetXml() {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kqyang): Should we set @start unconditionally to 0?
|
if (mpd_options_.mpd_type == MpdType::kDynamic ||
|
||||||
if (mpd_options_.mpd_type == MpdType::kDynamic) {
|
start_time_in_seconds_ != 0) {
|
||||||
// This is the only Period and it is a regular period.
|
period.SetStringAttribute("start",
|
||||||
period.SetStringAttribute("start", "PT0S");
|
SecondsToXmlDuration(start_time_in_seconds_));
|
||||||
}
|
}
|
||||||
return period.PassScopedPtr();
|
return period.PassScopedPtr();
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,13 +53,20 @@ class Period {
|
||||||
/// @return The list of AdaptationSets in this Period.
|
/// @return The list of AdaptationSets in this Period.
|
||||||
const std::list<AdaptationSet*> GetAdaptationSets() const;
|
const std::list<AdaptationSet*> GetAdaptationSets() const;
|
||||||
|
|
||||||
|
/// @return The start time of this Period.
|
||||||
|
double start_time_in_seconds() const { return start_time_in_seconds_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/// @param period_id is an ID number for this Period.
|
||||||
|
/// @param start_time_in_seconds is the start time for this Period.
|
||||||
/// @param mpd_options is the options for this MPD.
|
/// @param mpd_options is the options for this MPD.
|
||||||
/// @param adaptation_set_counter is a counter for assigning ID numbers to
|
/// @param adaptation_set_counter is a counter for assigning ID numbers to
|
||||||
/// AdaptationSet. It can not be NULL.
|
/// AdaptationSet. It can not be NULL.
|
||||||
/// @param representation_counter is a counter for assigning ID numbers to
|
/// @param representation_counter is a counter for assigning ID numbers to
|
||||||
/// Representation. It can not be NULL.
|
/// Representation. It can not be NULL.
|
||||||
Period(const MpdOptions& mpd_options,
|
Period(uint32_t period_id,
|
||||||
|
double start_time_in_seconds,
|
||||||
|
const MpdOptions& mpd_options,
|
||||||
base::AtomicSequenceNumber* adaptation_set_counter,
|
base::AtomicSequenceNumber* adaptation_set_counter,
|
||||||
base::AtomicSequenceNumber* representation_counter);
|
base::AtomicSequenceNumber* representation_counter);
|
||||||
|
|
||||||
|
@ -93,6 +100,8 @@ class Period {
|
||||||
const MediaInfo& media_info,
|
const MediaInfo& media_info,
|
||||||
uint32_t* original_adaptation_set_id);
|
uint32_t* original_adaptation_set_id);
|
||||||
|
|
||||||
|
const uint32_t id_;
|
||||||
|
const double start_time_in_seconds_;
|
||||||
const MpdOptions& mpd_options_;
|
const MpdOptions& mpd_options_;
|
||||||
base::AtomicSequenceNumber* const adaptation_set_counter_;
|
base::AtomicSequenceNumber* const adaptation_set_counter_;
|
||||||
base::AtomicSequenceNumber* const representation_counter_;
|
base::AtomicSequenceNumber* const representation_counter_;
|
||||||
|
|
|
@ -25,6 +25,8 @@ using ::testing::UnorderedElementsAre;
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace {
|
namespace {
|
||||||
|
const uint32_t kDefaultPeriodId = 9u;
|
||||||
|
const double kDefaultPeriodStartTime = 5.6;
|
||||||
const uint32_t kDefaultAdaptationSetId = 0u;
|
const uint32_t kDefaultAdaptationSetId = 0u;
|
||||||
const uint32_t kTrickPlayAdaptationSetId = 1u;
|
const uint32_t kTrickPlayAdaptationSetId = 1u;
|
||||||
|
|
||||||
|
@ -75,7 +77,11 @@ MATCHER_P(ContentProtectionElementEq, expected, "") {
|
||||||
class TestablePeriod : public Period {
|
class TestablePeriod : public Period {
|
||||||
public:
|
public:
|
||||||
TestablePeriod(const MpdOptions& mpd_options)
|
TestablePeriod(const MpdOptions& mpd_options)
|
||||||
: Period(mpd_options, &sequence_number_, &sequence_number_) {}
|
: Period(kDefaultPeriodId,
|
||||||
|
kDefaultPeriodStartTime,
|
||||||
|
mpd_options,
|
||||||
|
&sequence_number_,
|
||||||
|
&sequence_number_) {}
|
||||||
|
|
||||||
MOCK_METHOD4(NewAdaptationSet,
|
MOCK_METHOD4(NewAdaptationSet,
|
||||||
std::unique_ptr<AdaptationSet>(
|
std::unique_ptr<AdaptationSet>(
|
||||||
|
@ -133,7 +139,7 @@ TEST_P(PeriodTest, GetXml) {
|
||||||
content_protection_in_adaptation_set_));
|
content_protection_in_adaptation_set_));
|
||||||
|
|
||||||
const char kExpectedXml[] =
|
const char kExpectedXml[] =
|
||||||
"<Period id=\"0\">"
|
"<Period id=\"9\" start=\"PT5.6S\">"
|
||||||
// ContentType and Representation elements are populated after
|
// ContentType and Representation elements are populated after
|
||||||
// Representation::Init() is called.
|
// Representation::Init() is called.
|
||||||
" <AdaptationSet id=\"0\" contentType=\"\"/>"
|
" <AdaptationSet id=\"0\" contentType=\"\"/>"
|
||||||
|
@ -164,7 +170,7 @@ TEST_P(PeriodTest, DynamicMpdGetXml) {
|
||||||
content_protection_in_adaptation_set_));
|
content_protection_in_adaptation_set_));
|
||||||
|
|
||||||
const char kExpectedXml[] =
|
const char kExpectedXml[] =
|
||||||
"<Period id=\"0\" start=\"PT0S\">"
|
"<Period id=\"9\" start=\"PT5.6S\">"
|
||||||
// ContentType and Representation elements are populated after
|
// ContentType and Representation elements are populated after
|
||||||
// Representation::Init() is called.
|
// Representation::Init() is called.
|
||||||
" <AdaptationSet id=\"0\" contentType=\"\"/>"
|
" <AdaptationSet id=\"0\" contentType=\"\"/>"
|
||||||
|
|
|
@ -115,6 +115,24 @@ Representation::Representation(
|
||||||
state_change_listener_(std::move(state_change_listener)),
|
state_change_listener_(std::move(state_change_listener)),
|
||||||
output_suppression_flags_(0) {}
|
output_suppression_flags_(0) {}
|
||||||
|
|
||||||
|
Representation::Representation(
|
||||||
|
const Representation& representation,
|
||||||
|
uint64_t presentation_time_offset,
|
||||||
|
std::unique_ptr<RepresentationStateChangeListener> state_change_listener)
|
||||||
|
: Representation(representation.media_info_,
|
||||||
|
representation.mpd_options_,
|
||||||
|
representation.id_,
|
||||||
|
std::move(state_change_listener)) {
|
||||||
|
mime_type_ = representation.mime_type_;
|
||||||
|
codecs_ = representation.codecs_;
|
||||||
|
|
||||||
|
start_number_ = representation.start_number_;
|
||||||
|
for (const SegmentInfo& segment_info : representation.segment_infos_)
|
||||||
|
start_number_ += segment_info.repeat + 1;
|
||||||
|
|
||||||
|
media_info_.set_presentation_time_offset(presentation_time_offset);
|
||||||
|
}
|
||||||
|
|
||||||
Representation::~Representation() {}
|
Representation::~Representation() {}
|
||||||
|
|
||||||
bool Representation::Init() {
|
bool Representation::Init() {
|
||||||
|
@ -205,6 +223,10 @@ void Representation::SetSampleDuration(uint32_t sample_duration) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MediaInfo& Representation::GetMediaInfo() const {
|
||||||
|
return media_info_;
|
||||||
|
}
|
||||||
|
|
||||||
// Uses info in |media_info_| and |content_protection_elements_| to create a
|
// Uses info in |media_info_| and |content_protection_elements_| to create a
|
||||||
// "Representation" node.
|
// "Representation" node.
|
||||||
// MPD schema has strict ordering. The following must be done in order.
|
// MPD schema has strict ordering. The following must be done in order.
|
||||||
|
|
|
@ -112,6 +112,9 @@ class Representation {
|
||||||
/// @param sample_duration is the duration of a sample.
|
/// @param sample_duration is the duration of a sample.
|
||||||
virtual void SetSampleDuration(uint32_t sample_duration);
|
virtual void SetSampleDuration(uint32_t sample_duration);
|
||||||
|
|
||||||
|
/// @return MediaInfo for the Representation.
|
||||||
|
virtual const MediaInfo& GetMediaInfo() const;
|
||||||
|
|
||||||
/// @return Copy of <Representation>.
|
/// @return Copy of <Representation>.
|
||||||
xml::scoped_xml_ptr<xmlNode> GetXml();
|
xml::scoped_xml_ptr<xmlNode> GetXml();
|
||||||
|
|
||||||
|
@ -150,6 +153,16 @@ class Representation {
|
||||||
uint32_t representation_id,
|
uint32_t representation_id,
|
||||||
std::unique_ptr<RepresentationStateChangeListener> state_change_listener);
|
std::unique_ptr<RepresentationStateChangeListener> state_change_listener);
|
||||||
|
|
||||||
|
/// @param representation points to the original Representation to be cloned.
|
||||||
|
/// @param presentation_time_offset is the presentation time offset for the
|
||||||
|
/// new Representation.
|
||||||
|
/// @param state_change_listener is an event handler for state changes to
|
||||||
|
/// the representation. If null, no event handler registered.
|
||||||
|
Representation(
|
||||||
|
const Representation& representation,
|
||||||
|
uint64_t presentation_time_offset,
|
||||||
|
std::unique_ptr<RepresentationStateChangeListener> state_change_listener);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Representation(const Representation&) = delete;
|
Representation(const Representation&) = delete;
|
||||||
Representation& operator=(const Representation&) = delete;
|
Representation& operator=(const Representation&) = delete;
|
||||||
|
@ -181,6 +194,7 @@ class Representation {
|
||||||
// any logic using this can assume only one set.
|
// any logic using this can assume only one set.
|
||||||
MediaInfo media_info_;
|
MediaInfo media_info_;
|
||||||
std::list<ContentProtectionElement> content_protection_elements_;
|
std::list<ContentProtectionElement> content_protection_elements_;
|
||||||
|
// TODO(kqyang): Address sliding window issue with multiple periods.
|
||||||
std::list<SegmentInfo> segment_infos_;
|
std::list<SegmentInfo> segment_infos_;
|
||||||
|
|
||||||
const uint32_t id_;
|
const uint32_t id_;
|
||||||
|
|
|
@ -52,6 +52,16 @@ class RepresentationTest : public ::testing::Test {
|
||||||
std::move(state_change_listener)));
|
std::move(state_change_listener)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Representation> CopyRepresentation(
|
||||||
|
const Representation& representation,
|
||||||
|
uint64_t presentation_time_offset,
|
||||||
|
std::unique_ptr<RepresentationStateChangeListener>
|
||||||
|
state_change_listener) {
|
||||||
|
return std::unique_ptr<Representation>(
|
||||||
|
new Representation(representation, presentation_time_offset,
|
||||||
|
std::move(state_change_listener)));
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<RepresentationStateChangeListener> NoListener() {
|
std::unique_ptr<RepresentationStateChangeListener> NoListener() {
|
||||||
return std::unique_ptr<RepresentationStateChangeListener>();
|
return std::unique_ptr<RepresentationStateChangeListener>();
|
||||||
}
|
}
|
||||||
|
@ -484,6 +494,35 @@ TEST_F(SegmentTemplateTest, OneSegmentNormal) {
|
||||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml));
|
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SegmentTemplateTest, RepresentationClone) {
|
||||||
|
MediaInfo media_info = ConvertToMediaInfo(GetDefaultMediaInfo());
|
||||||
|
media_info.set_segment_template("$Number$.mp4");
|
||||||
|
representation_ =
|
||||||
|
CreateRepresentation(media_info, kAnyRepresentationId, NoListener());
|
||||||
|
ASSERT_TRUE(representation_->Init());
|
||||||
|
|
||||||
|
const uint64_t kStartTime = 0;
|
||||||
|
const uint64_t kDuration = 10;
|
||||||
|
const uint64_t kSize = 128;
|
||||||
|
AddSegments(kStartTime, kDuration, kSize, 0);
|
||||||
|
|
||||||
|
const uint64_t kPresentationTimeOffset = 100;
|
||||||
|
auto cloned_representation = CopyRepresentation(
|
||||||
|
*representation_, kPresentationTimeOffset, NoListener());
|
||||||
|
const char kExpectedXml[] =
|
||||||
|
"<Representation id=\"1\" bandwidth=\"0\" "
|
||||||
|
" codecs=\"avc1.010101\" mimeType=\"video/mp4\" sar=\"1:1\" "
|
||||||
|
" width=\"720\" height=\"480\" frameRate=\"10/5\">\n"
|
||||||
|
" <SegmentTemplate presentationTimeOffset=\"100\" timescale=\"1000\" "
|
||||||
|
" initialization=\"init.mp4\" media=\"$Number$.mp4\" "
|
||||||
|
" startNumber=\"2\">\n"
|
||||||
|
" <SegmentTimeline/>\n"
|
||||||
|
" </SegmentTemplate>\n"
|
||||||
|
"</Representation>\n";
|
||||||
|
EXPECT_THAT(cloned_representation->GetXml().get(),
|
||||||
|
XmlNodeEqual(kExpectedXml));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SegmentTemplateTest, GetEarliestTimestamp) {
|
TEST_F(SegmentTemplateTest, GetEarliestTimestamp) {
|
||||||
double earliest_timestamp;
|
double earliest_timestamp;
|
||||||
// No segments.
|
// No segments.
|
||||||
|
|
|
@ -41,32 +41,17 @@ bool SimpleMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
||||||
if (content_type == kContentTypeUnknown)
|
if (content_type == kContentTypeUnknown)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
base::AutoLock auto_lock(lock_);
|
|
||||||
if (!period_)
|
|
||||||
period_ = mpd_builder_->AddPeriod();
|
|
||||||
AdaptationSet* adaptation_set = period_->GetOrCreateAdaptationSet(
|
|
||||||
media_info, content_protection_in_adaptation_set_);
|
|
||||||
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 =
|
const Representation* kNoOriginalRepresentation = nullptr;
|
||||||
adaptation_set->AddRepresentation(adjusted_media_info);
|
const double kPeriodStartTimeSeconds = 0.0;
|
||||||
|
|
||||||
|
base::AutoLock auto_lock(lock_);
|
||||||
|
const Representation* representation = AddRepresentationToPeriod(
|
||||||
|
adjusted_media_info, kNoOriginalRepresentation, kPeriodStartTimeSeconds);
|
||||||
if (!representation)
|
if (!representation)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (content_protection_in_adaptation_set_) {
|
|
||||||
// ContentProtection elements are already added to AdaptationSet above.
|
|
||||||
// Use RepresentationId to AdaptationSet map to update ContentProtection
|
|
||||||
// in AdaptationSet in NotifyEncryptionUpdate.
|
|
||||||
representation_id_to_adaptation_set_[representation->id()] = adaptation_set;
|
|
||||||
} else {
|
|
||||||
AddContentProtectionElements(media_info, representation);
|
|
||||||
}
|
|
||||||
|
|
||||||
*container_id = representation->id();
|
*container_id = representation->id();
|
||||||
DCHECK(!ContainsKey(representation_map_, representation->id()));
|
|
||||||
representation_map_[representation->id()] = representation;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +83,30 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id,
|
||||||
|
|
||||||
bool SimpleMpdNotifier::NotifyCueEvent(uint32_t container_id,
|
bool SimpleMpdNotifier::NotifyCueEvent(uint32_t container_id,
|
||||||
uint64_t timestamp) {
|
uint64_t timestamp) {
|
||||||
NOTIMPLEMENTED();
|
base::AutoLock auto_lock(lock_);
|
||||||
|
auto it = representation_map_.find(container_id);
|
||||||
|
if (it == representation_map_.end()) {
|
||||||
|
LOG(ERROR) << "Unexpected container_id: " << container_id;
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
Representation* original_representation = it->second;
|
||||||
|
AdaptationSet* original_adaptation_set =
|
||||||
|
representation_id_to_adaptation_set_[container_id];
|
||||||
|
|
||||||
|
const MediaInfo& media_info = original_representation->GetMediaInfo();
|
||||||
|
const double period_start_time_seconds =
|
||||||
|
static_cast<double>(timestamp) / media_info.reference_time_scale();
|
||||||
|
const Representation* new_representation = AddRepresentationToPeriod(
|
||||||
|
media_info, original_representation, period_start_time_seconds);
|
||||||
|
if (!new_representation)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// TODO(kqyang): Pass the ID to GetOrCreateAdaptationSet instead?
|
||||||
|
AdaptationSet* new_adaptation_set =
|
||||||
|
representation_id_to_adaptation_set_[container_id];
|
||||||
|
DCHECK(new_adaptation_set);
|
||||||
|
new_adaptation_set->set_id(original_adaptation_set->id());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SimpleMpdNotifier::NotifyEncryptionUpdate(
|
bool SimpleMpdNotifier::NotifyEncryptionUpdate(
|
||||||
|
@ -131,4 +138,38 @@ bool SimpleMpdNotifier::Flush() {
|
||||||
return WriteMpdToFile(output_path_, mpd_builder_.get());
|
return WriteMpdToFile(output_path_, mpd_builder_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Representation* SimpleMpdNotifier::AddRepresentationToPeriod(
|
||||||
|
const MediaInfo& media_info,
|
||||||
|
const Representation* original_representation,
|
||||||
|
double period_start_time_seconds) {
|
||||||
|
Period* period = mpd_builder_->GetOrCreatePeriod(period_start_time_seconds);
|
||||||
|
DCHECK(period);
|
||||||
|
|
||||||
|
AdaptationSet* adaptation_set = period->GetOrCreateAdaptationSet(
|
||||||
|
media_info, content_protection_in_adaptation_set_);
|
||||||
|
DCHECK(adaptation_set);
|
||||||
|
|
||||||
|
Representation* representation = nullptr;
|
||||||
|
if (original_representation) {
|
||||||
|
representation = adaptation_set->CopyRepresentationWithTimeOffset(
|
||||||
|
*original_representation,
|
||||||
|
period->start_time_in_seconds() * media_info.reference_time_scale());
|
||||||
|
} else {
|
||||||
|
representation = adaptation_set->AddRepresentation(media_info);
|
||||||
|
}
|
||||||
|
if (!representation)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
if (content_protection_in_adaptation_set_) {
|
||||||
|
// ContentProtection elements are already added to AdaptationSet above.
|
||||||
|
// Use RepresentationId to AdaptationSet map to update ContentProtection
|
||||||
|
// in AdaptationSet in NotifyEncryptionUpdate.
|
||||||
|
representation_id_to_adaptation_set_[representation->id()] = adaptation_set;
|
||||||
|
} else {
|
||||||
|
AddContentProtectionElements(media_info, representation);
|
||||||
|
}
|
||||||
|
representation_map_[representation->id()] = representation;
|
||||||
|
return representation;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -20,7 +20,6 @@ namespace shaka {
|
||||||
|
|
||||||
class AdaptationSet;
|
class AdaptationSet;
|
||||||
class MpdBuilder;
|
class MpdBuilder;
|
||||||
class Period;
|
|
||||||
class Representation;
|
class Representation;
|
||||||
|
|
||||||
struct MpdOptions;
|
struct MpdOptions;
|
||||||
|
@ -39,7 +38,7 @@ class SimpleMpdNotifier : public MpdNotifier {
|
||||||
bool NotifyNewContainer(const MediaInfo& media_info, uint32_t* id) override;
|
bool NotifyNewContainer(const MediaInfo& media_info, uint32_t* id) override;
|
||||||
bool NotifySampleDuration(uint32_t container_id,
|
bool NotifySampleDuration(uint32_t container_id,
|
||||||
uint32_t sample_duration) override;
|
uint32_t sample_duration) override;
|
||||||
bool NotifyNewSegment(uint32_t id,
|
bool NotifyNewSegment(uint32_t container_id,
|
||||||
uint64_t start_time,
|
uint64_t start_time,
|
||||||
uint64_t duration,
|
uint64_t duration,
|
||||||
uint64_t size) override;
|
uint64_t size) override;
|
||||||
|
@ -57,6 +56,17 @@ class SimpleMpdNotifier : public MpdNotifier {
|
||||||
|
|
||||||
friend class SimpleMpdNotifierTest;
|
friend class SimpleMpdNotifierTest;
|
||||||
|
|
||||||
|
// Add a new representation. If |original_representation| is not nullptr, the
|
||||||
|
// new Representation will clone from it; otherwise the new Representation is
|
||||||
|
// created from |media_info|.
|
||||||
|
// The new Representation will be added to Period with the specified start
|
||||||
|
// time.
|
||||||
|
// Returns the new Representation on success; otherwise a nullptr is returned.
|
||||||
|
Representation* AddRepresentationToPeriod(
|
||||||
|
const MediaInfo& media_info,
|
||||||
|
const Representation* original_representation,
|
||||||
|
double period_start_time_seconds);
|
||||||
|
|
||||||
// Testing only method. Returns a pointer to MpdBuilder.
|
// Testing only method. Returns a pointer to MpdBuilder.
|
||||||
MpdBuilder* MpdBuilderForTesting() const { return mpd_builder_.get(); }
|
MpdBuilder* MpdBuilderForTesting() const { return mpd_builder_.get(); }
|
||||||
|
|
||||||
|
@ -68,7 +78,6 @@ 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; // owned by |mpd_builder_|.
|
|
||||||
bool content_protection_in_adaptation_set_ = true;
|
bool content_protection_in_adaptation_set_ = true;
|
||||||
base::Lock lock_;
|
base::Lock lock_;
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,16 @@ namespace shaka {
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::Eq;
|
using ::testing::Eq;
|
||||||
|
using ::testing::Ref;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
|
using ::testing::ReturnRef;
|
||||||
using ::testing::StrEq;
|
using ::testing::StrEq;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
const uint32_t kDefaultPeriodId = 0u;
|
||||||
|
const double kDefaultPeriodStartTime = 0.0;
|
||||||
const uint32_t kDefaultAdaptationSetId = 0u;
|
const uint32_t kDefaultAdaptationSetId = 0u;
|
||||||
|
const uint32_t kDefaultTimeScale = 10;
|
||||||
const bool kContentProtectionInAdaptationSet = true;
|
const bool kContentProtectionInAdaptationSet = true;
|
||||||
|
|
||||||
MATCHER_P(EqualsProto, message, "") {
|
MATCHER_P(EqualsProto, message, "") {
|
||||||
|
@ -36,7 +41,8 @@ MATCHER_P(EqualsProto, message, "") {
|
||||||
class SimpleMpdNotifierTest : public ::testing::Test {
|
class SimpleMpdNotifierTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
SimpleMpdNotifierTest()
|
SimpleMpdNotifierTest()
|
||||||
: default_mock_period_(new MockPeriod),
|
: default_mock_period_(
|
||||||
|
new MockPeriod(kDefaultPeriodId, kDefaultPeriodStartTime)),
|
||||||
default_mock_adaptation_set_(
|
default_mock_adaptation_set_(
|
||||||
new MockAdaptationSet(kDefaultAdaptationSetId)) {}
|
new MockAdaptationSet(kDefaultAdaptationSetId)) {}
|
||||||
|
|
||||||
|
@ -57,6 +63,7 @@ class SimpleMpdNotifierTest : public ::testing::Test {
|
||||||
"}\n"
|
"}\n"
|
||||||
"container_type: 1\n";
|
"container_type: 1\n";
|
||||||
valid_media_info1_ = ConvertToMediaInfo(kValidMediaInfo);
|
valid_media_info1_ = ConvertToMediaInfo(kValidMediaInfo);
|
||||||
|
valid_media_info1_.set_reference_time_scale(kDefaultTimeScale);
|
||||||
valid_media_info2_ = valid_media_info1_;
|
valid_media_info2_ = valid_media_info1_;
|
||||||
valid_media_info2_.mutable_video_info()->set_width(960);
|
valid_media_info2_.mutable_video_info()->set_width(960);
|
||||||
valid_media_info3_ = valid_media_info1_;
|
valid_media_info3_ = valid_media_info1_;
|
||||||
|
@ -100,7 +107,7 @@ 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, AddPeriod())
|
EXPECT_CALL(*mock_mpd_builder, GetOrCreatePeriod(_))
|
||||||
.WillOnce(Return(default_mock_period_.get()));
|
.WillOnce(Return(default_mock_period_.get()));
|
||||||
EXPECT_CALL(*default_mock_period_,
|
EXPECT_CALL(*default_mock_period_,
|
||||||
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
|
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
|
||||||
|
@ -128,7 +135,7 @@ 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, AddPeriod())
|
EXPECT_CALL(*mock_mpd_builder, GetOrCreatePeriod(_))
|
||||||
.WillOnce(Return(default_mock_period_.get()));
|
.WillOnce(Return(default_mock_period_.get()));
|
||||||
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
|
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
|
||||||
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
|
@ -168,7 +175,7 @@ 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, AddPeriod())
|
EXPECT_CALL(*mock_mpd_builder, GetOrCreatePeriod(_))
|
||||||
.WillOnce(Return(default_mock_period_.get()));
|
.WillOnce(Return(default_mock_period_.get()));
|
||||||
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
|
EXPECT_CALL(*default_mock_period_, GetOrCreateAdaptationSet(_, _))
|
||||||
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||||
|
@ -190,6 +197,58 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewSegment) {
|
||||||
kSegmentDuration, kSegmentSize));
|
kSegmentDuration, kSegmentSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SimpleMpdNotifierTest, NotifyCueEvent) {
|
||||||
|
SimpleMpdNotifier notifier(empty_mpd_option_);
|
||||||
|
|
||||||
|
const uint32_t kRepresentationId = 123u;
|
||||||
|
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
|
||||||
|
MockMpdBuilder* mock_mpd_builder_ptr = mock_mpd_builder.get();
|
||||||
|
|
||||||
|
std::unique_ptr<MockPeriod> mock_period(
|
||||||
|
new MockPeriod(kDefaultPeriodId, kDefaultPeriodStartTime));
|
||||||
|
std::unique_ptr<MockAdaptationSet> mock_adaptation_set(
|
||||||
|
new MockAdaptationSet(kDefaultAdaptationSetId));
|
||||||
|
std::unique_ptr<MockRepresentation> mock_representation(
|
||||||
|
new MockRepresentation(kRepresentationId));
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_mpd_builder, GetOrCreatePeriod(Eq(0.0)))
|
||||||
|
.WillOnce(Return(mock_period.get()));
|
||||||
|
EXPECT_CALL(*mock_period,
|
||||||
|
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
|
||||||
|
.WillOnce(Return(mock_adaptation_set.get()));
|
||||||
|
EXPECT_CALL(*mock_adaptation_set, AddRepresentation(_))
|
||||||
|
.WillOnce(Return(mock_representation.get()));
|
||||||
|
|
||||||
|
uint32_t container_id;
|
||||||
|
SetMpdBuilder(¬ifier, std::move(mock_mpd_builder));
|
||||||
|
EXPECT_TRUE(notifier.NotifyNewContainer(valid_media_info1_, &container_id));
|
||||||
|
EXPECT_EQ(kRepresentationId, container_id);
|
||||||
|
|
||||||
|
const uint32_t kAnotherPeriodId = 2u;
|
||||||
|
const double kArbitraryPeriodStartTime = 100; // Value does not matter.
|
||||||
|
std::unique_ptr<MockPeriod> mock_period2(
|
||||||
|
new MockPeriod(kAnotherPeriodId, kArbitraryPeriodStartTime));
|
||||||
|
std::unique_ptr<MockAdaptationSet> mock_adaptation_set2(
|
||||||
|
new MockAdaptationSet(kDefaultAdaptationSetId));
|
||||||
|
std::unique_ptr<MockRepresentation> mock_representation2(
|
||||||
|
new MockRepresentation(kRepresentationId));
|
||||||
|
|
||||||
|
const uint64_t kCueEventTimestamp = 1000;
|
||||||
|
EXPECT_CALL(*mock_representation, GetMediaInfo())
|
||||||
|
.WillOnce(ReturnRef(valid_media_info1_));
|
||||||
|
EXPECT_CALL(*mock_mpd_builder_ptr,
|
||||||
|
GetOrCreatePeriod(Eq(kCueEventTimestamp / kDefaultTimeScale)))
|
||||||
|
.WillOnce(Return(mock_period2.get()));
|
||||||
|
EXPECT_CALL(*mock_period2,
|
||||||
|
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
|
||||||
|
.WillOnce(Return(mock_adaptation_set2.get()));
|
||||||
|
EXPECT_CALL(*mock_adaptation_set2,
|
||||||
|
CopyRepresentationWithTimeOffset(Ref(*mock_representation),
|
||||||
|
kCueEventTimestamp))
|
||||||
|
.WillOnce(Return(mock_representation2.get()));
|
||||||
|
EXPECT_TRUE(notifier.NotifyCueEvent(container_id, kCueEventTimestamp));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SimpleMpdNotifierTest,
|
TEST_F(SimpleMpdNotifierTest,
|
||||||
ContentProtectionInAdaptationSetUpdateEncryption) {
|
ContentProtectionInAdaptationSetUpdateEncryption) {
|
||||||
MpdOptions mpd_options = empty_mpd_option_;
|
MpdOptions mpd_options = empty_mpd_option_;
|
||||||
|
@ -202,7 +261,7 @@ TEST_F(SimpleMpdNotifierTest,
|
||||||
std::unique_ptr<MockRepresentation> mock_representation(
|
std::unique_ptr<MockRepresentation> mock_representation(
|
||||||
new MockRepresentation(kRepresentationId));
|
new MockRepresentation(kRepresentationId));
|
||||||
|
|
||||||
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
|
EXPECT_CALL(*mock_mpd_builder, GetOrCreatePeriod(_))
|
||||||
.WillOnce(Return(default_mock_period_.get()));
|
.WillOnce(Return(default_mock_period_.get()));
|
||||||
EXPECT_CALL(
|
EXPECT_CALL(
|
||||||
*default_mock_period_,
|
*default_mock_period_,
|
||||||
|
@ -245,7 +304,7 @@ TEST_F(SimpleMpdNotifierTest,
|
||||||
std::unique_ptr<MockRepresentation> mock_representation(
|
std::unique_ptr<MockRepresentation> mock_representation(
|
||||||
new MockRepresentation(kRepresentationId));
|
new MockRepresentation(kRepresentationId));
|
||||||
|
|
||||||
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
|
EXPECT_CALL(*mock_mpd_builder, GetOrCreatePeriod(_))
|
||||||
.WillOnce(Return(default_mock_period_.get()));
|
.WillOnce(Return(default_mock_period_.get()));
|
||||||
EXPECT_CALL(
|
EXPECT_CALL(
|
||||||
*default_mock_period_,
|
*default_mock_period_,
|
||||||
|
@ -291,8 +350,9 @@ TEST_F(SimpleMpdNotifierTest, MultipleMediaInfo) {
|
||||||
std::unique_ptr<MockRepresentation> representation3(
|
std::unique_ptr<MockRepresentation> representation3(
|
||||||
new MockRepresentation(3));
|
new MockRepresentation(3));
|
||||||
|
|
||||||
EXPECT_CALL(*mock_mpd_builder, AddPeriod())
|
EXPECT_CALL(*mock_mpd_builder, GetOrCreatePeriod(_))
|
||||||
.WillOnce(Return(default_mock_period_.get()));
|
.Times(3)
|
||||||
|
.WillRepeatedly(Return(default_mock_period_.get()));
|
||||||
|
|
||||||
EXPECT_CALL(*default_mock_period_,
|
EXPECT_CALL(*default_mock_period_,
|
||||||
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
|
GetOrCreateAdaptationSet(EqualsProto(valid_media_info1_), _))
|
||||||
|
|
|
@ -300,6 +300,11 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
|
||||||
media_info.reference_time_scale());
|
media_info.reference_time_scale());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media_info.has_presentation_time_offset()) {
|
||||||
|
segment_base.SetIntegerAttribute("presentationTimeOffset",
|
||||||
|
media_info.presentation_time_offset());
|
||||||
|
}
|
||||||
|
|
||||||
if (media_info.has_init_range()) {
|
if (media_info.has_init_range()) {
|
||||||
XmlNode initialization("Initialization");
|
XmlNode initialization("Initialization");
|
||||||
initialization.SetStringAttribute("range",
|
initialization.SetStringAttribute("range",
|
||||||
|
@ -326,6 +331,11 @@ bool RepresentationXmlNode::AddLiveOnlyInfo(
|
||||||
media_info.reference_time_scale());
|
media_info.reference_time_scale());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media_info.has_presentation_time_offset()) {
|
||||||
|
segment_template.SetIntegerAttribute("presentationTimeOffset",
|
||||||
|
media_info.presentation_time_offset());
|
||||||
|
}
|
||||||
|
|
||||||
if (media_info.has_init_segment_name()) {
|
if (media_info.has_init_segment_name()) {
|
||||||
// The spec does not allow '$Number$' and '$Time$' in initialization
|
// The spec does not allow '$Number$' and '$Time$' in initialization
|
||||||
// attribute.
|
// attribute.
|
||||||
|
|
|
@ -28,7 +28,9 @@ bool XmlEqual(const std::string& xml1, xmlNodePtr xml2);
|
||||||
std::string XmlNodeToString(xmlNodePtr xml_node);
|
std::string XmlNodeToString(xmlNodePtr xml_node);
|
||||||
|
|
||||||
/// Match an xmlNodePtr with an xml in string representation.
|
/// Match an xmlNodePtr with an xml in string representation.
|
||||||
MATCHER_P(XmlNodeEqual, xml, "") {
|
MATCHER_P(XmlNodeEqual,
|
||||||
|
xml,
|
||||||
|
std::string("xml node equal (ignore extra white spaces)\n") + xml) {
|
||||||
*result_listener << "\n" << XmlNodeToString(arg);
|
*result_listener << "\n" << XmlNodeToString(arg);
|
||||||
return XmlEqual(xml, arg);
|
return XmlEqual(xml, arg);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue