Respect MPD@timeShiftBufferDepth
- Only Segments with end time in range [NOW - timeShiftBufferDepth, NOW] get listed as S elements under TemplateTimeline element. Any old segments do not get listed. - Also adding tests Change-Id: I52df9acaec107610757d809ac6c9cb13592d6f37
This commit is contained in:
parent
1927109818
commit
4668770093
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "mpd/base/mpd_builder.h"
|
#include "mpd/base/mpd_builder.h"
|
||||||
|
|
||||||
|
#include <list>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
|
@ -109,6 +110,50 @@ void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32 GetTimeScale(const MediaInfo& media_info) {
|
||||||
|
if (media_info.has_reference_time_scale()) {
|
||||||
|
return media_info.reference_time_scale();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media_info.video_info_size() > 0) {
|
||||||
|
return media_info.video_info(0).time_scale();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media_info.audio_info_size() > 0) {
|
||||||
|
return media_info.audio_info(0).time_scale();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(WARNING) << "No timescale specified, using 1 as timescale.";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 LastSegmentStartTime(const SegmentInfo& segment_info) {
|
||||||
|
return segment_info.start_time + segment_info.duration * segment_info.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is equal to |segment_info| end time
|
||||||
|
uint64 LastSegmentEndTime(const SegmentInfo& segment_info) {
|
||||||
|
return segment_info.start_time +
|
||||||
|
segment_info.duration * (segment_info.repeat + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 LatestSegmentStartTime(const std::list<SegmentInfo>& segments) {
|
||||||
|
DCHECK(!segments.empty());
|
||||||
|
const SegmentInfo& latest_segment = segments.back();
|
||||||
|
return LastSegmentStartTime(latest_segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given |timeshift_limit|, finds out the number of segments that are no longer
|
||||||
|
// valid and should be removed from |segment_info|.
|
||||||
|
int SearchTimedOutRepeatIndex(uint64 timeshift_limit,
|
||||||
|
const SegmentInfo& segment_info) {
|
||||||
|
DCHECK_LE(timeshift_limit, LastSegmentEndTime(segment_info));
|
||||||
|
if (timeshift_limit < segment_info.start_time)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (timeshift_limit - segment_info.start_time) / segment_info.duration;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MpdOptions::MpdOptions()
|
MpdOptions::MpdOptions()
|
||||||
|
@ -124,7 +169,7 @@ MpdOptions::~MpdOptions() {}
|
||||||
|
|
||||||
MpdBuilder::MpdBuilder(MpdType type, const MpdOptions& mpd_options)
|
MpdBuilder::MpdBuilder(MpdType type, const MpdOptions& mpd_options)
|
||||||
: type_(type),
|
: type_(type),
|
||||||
options_(mpd_options),
|
mpd_options_(mpd_options),
|
||||||
adaptation_sets_deleter_(&adaptation_sets_) {}
|
adaptation_sets_deleter_(&adaptation_sets_) {}
|
||||||
|
|
||||||
MpdBuilder::~MpdBuilder() {}
|
MpdBuilder::~MpdBuilder() {}
|
||||||
|
@ -137,7 +182,7 @@ void MpdBuilder::AddBaseUrl(const std::string& base_url) {
|
||||||
AdaptationSet* MpdBuilder::AddAdaptationSet() {
|
AdaptationSet* MpdBuilder::AddAdaptationSet() {
|
||||||
base::AutoLock scoped_lock(lock_);
|
base::AutoLock scoped_lock(lock_);
|
||||||
scoped_ptr<AdaptationSet> adaptation_set(new AdaptationSet(
|
scoped_ptr<AdaptationSet> adaptation_set(new AdaptationSet(
|
||||||
adaptation_set_counter_.GetNext(), &representation_counter_));
|
adaptation_set_counter_.GetNext(), mpd_options_, &representation_counter_));
|
||||||
|
|
||||||
DCHECK(adaptation_set);
|
DCHECK(adaptation_set);
|
||||||
adaptation_sets_.push_back(adaptation_set.get());
|
adaptation_sets_.push_back(adaptation_set.get());
|
||||||
|
@ -285,31 +330,32 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
|
||||||
|
|
||||||
void MpdBuilder::SetMpdOptionsValues(XmlNode* mpd) {
|
void MpdBuilder::SetMpdOptionsValues(XmlNode* mpd) {
|
||||||
if (type_ == kStatic) {
|
if (type_ == kStatic) {
|
||||||
if (!options_.availability_start_time.empty()) {
|
if (!mpd_options_.availability_start_time.empty()) {
|
||||||
mpd->SetStringAttribute("availabilityStartTime",
|
mpd->SetStringAttribute("availabilityStartTime",
|
||||||
options_.availability_start_time);
|
mpd_options_.availability_start_time);
|
||||||
}
|
}
|
||||||
LOG_IF(WARNING, Positive(options_.minimum_update_period))
|
LOG_IF(WARNING, Positive(mpd_options_.minimum_update_period))
|
||||||
<< "minimumUpdatePeriod should not be present in 'static' profile. "
|
<< "minimumUpdatePeriod should not be present in 'static' profile. "
|
||||||
"Ignoring.";
|
"Ignoring.";
|
||||||
LOG_IF(WARNING, Positive(options_.time_shift_buffer_depth))
|
LOG_IF(WARNING, Positive(mpd_options_.time_shift_buffer_depth))
|
||||||
<< "timeShiftBufferDepth will not be used for 'static' profile. "
|
<< "timeShiftBufferDepth will not be used for 'static' profile. "
|
||||||
"Ignoring.";
|
"Ignoring.";
|
||||||
LOG_IF(WARNING, Positive(options_.suggested_presentation_delay))
|
LOG_IF(WARNING, Positive(mpd_options_.suggested_presentation_delay))
|
||||||
<< "suggestedPresentationDelay will not be used for 'static' profile. "
|
<< "suggestedPresentationDelay will not be used for 'static' profile. "
|
||||||
"Ignoring.";
|
"Ignoring.";
|
||||||
} else if (type_ == kDynamic) {
|
} else if (type_ == kDynamic) {
|
||||||
// 'availabilityStartTime' is required for dynamic profile, so use current
|
// 'availabilityStartTime' is required for dynamic profile, so use current
|
||||||
// time if not specified.
|
// time if not specified.
|
||||||
const std::string avail_start = !options_.availability_start_time.empty()
|
const std::string avail_start =
|
||||||
? options_.availability_start_time
|
!mpd_options_.availability_start_time.empty()
|
||||||
|
? mpd_options_.availability_start_time
|
||||||
: XmlDateTimeNow();
|
: XmlDateTimeNow();
|
||||||
mpd->SetStringAttribute("availabilityStartTime", avail_start);
|
mpd->SetStringAttribute("availabilityStartTime", avail_start);
|
||||||
|
|
||||||
if (Positive(options_.minimum_update_period)) {
|
if (Positive(mpd_options_.minimum_update_period)) {
|
||||||
mpd->SetStringAttribute(
|
mpd->SetStringAttribute(
|
||||||
"minimumUpdatePeriod",
|
"minimumUpdatePeriod",
|
||||||
SecondsToXmlDuration(options_.minimum_update_period));
|
SecondsToXmlDuration(mpd_options_.minimum_update_period));
|
||||||
} else {
|
} else {
|
||||||
// TODO(rkuroiwa): Set minimumUpdatePeriod to some default value.
|
// TODO(rkuroiwa): Set minimumUpdatePeriod to some default value.
|
||||||
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
|
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
|
||||||
|
@ -317,33 +363,36 @@ void MpdBuilder::SetMpdOptionsValues(XmlNode* mpd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SetIfPositive(
|
SetIfPositive(
|
||||||
"timeShiftBufferDepth", options_.time_shift_buffer_depth, mpd);
|
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth, mpd);
|
||||||
SetIfPositive("suggestedPresentationDelay",
|
SetIfPositive("suggestedPresentationDelay",
|
||||||
options_.suggested_presentation_delay,
|
mpd_options_.suggested_presentation_delay,
|
||||||
mpd);
|
mpd);
|
||||||
}
|
}
|
||||||
|
|
||||||
const double kDefaultMinBufferTime = 2.0;
|
const double kDefaultMinBufferTime = 2.0;
|
||||||
const double min_buffer_time = Positive(options_.min_buffer_time)
|
const double min_buffer_time = Positive(mpd_options_.min_buffer_time)
|
||||||
? options_.min_buffer_time
|
? mpd_options_.min_buffer_time
|
||||||
: kDefaultMinBufferTime;
|
: kDefaultMinBufferTime;
|
||||||
mpd->SetStringAttribute("minBufferTime",
|
mpd->SetStringAttribute("minBufferTime",
|
||||||
SecondsToXmlDuration(min_buffer_time));
|
SecondsToXmlDuration(min_buffer_time));
|
||||||
|
|
||||||
if (!options_.availability_end_time.empty()) {
|
if (!mpd_options_.availability_end_time.empty()) {
|
||||||
mpd->SetStringAttribute("availabilityEndTime",
|
mpd->SetStringAttribute("availabilityEndTime",
|
||||||
options_.availability_end_time);
|
mpd_options_.availability_end_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetIfPositive("maxSegmentDuration", options_.max_segment_duration, mpd);
|
SetIfPositive("maxSegmentDuration", mpd_options_.max_segment_duration, mpd);
|
||||||
SetIfPositive("maxSubsegmentDuration", options_.max_subsegment_duration, mpd);
|
SetIfPositive(
|
||||||
|
"maxSubsegmentDuration", mpd_options_.max_subsegment_duration, mpd);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdaptationSet::AdaptationSet(uint32 adaptation_set_id,
|
AdaptationSet::AdaptationSet(uint32 adaptation_set_id,
|
||||||
|
const MpdOptions& mpd_options,
|
||||||
base::AtomicSequenceNumber* counter)
|
base::AtomicSequenceNumber* counter)
|
||||||
: representations_deleter_(&representations_),
|
: representations_deleter_(&representations_),
|
||||||
representation_counter_(counter),
|
representation_counter_(counter),
|
||||||
id_(adaptation_set_id) {
|
id_(adaptation_set_id),
|
||||||
|
mpd_options_(mpd_options) {
|
||||||
DCHECK(counter);
|
DCHECK(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,8 +400,8 @@ AdaptationSet::~AdaptationSet() {}
|
||||||
|
|
||||||
Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) {
|
Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) {
|
||||||
base::AutoLock scoped_lock(lock_);
|
base::AutoLock scoped_lock(lock_);
|
||||||
scoped_ptr<Representation> representation(
|
scoped_ptr<Representation> representation(new Representation(
|
||||||
new Representation(media_info, representation_counter_->GetNext()));
|
media_info, mpd_options_, representation_counter_->GetNext()));
|
||||||
|
|
||||||
if (!representation->Init())
|
if (!representation->Init())
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -392,10 +441,14 @@ xml::ScopedXmlPtr<xmlNode>::type AdaptationSet::GetXml() {
|
||||||
return adaptation_set.PassScopedPtr();
|
return adaptation_set.PassScopedPtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
Representation::Representation(const MediaInfo& media_info, uint32 id)
|
Representation::Representation(const MediaInfo& media_info,
|
||||||
|
const MpdOptions& mpd_options,
|
||||||
|
uint32 id)
|
||||||
: media_info_(media_info),
|
: media_info_(media_info),
|
||||||
id_(id),
|
id_(id),
|
||||||
bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks) {}
|
bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks),
|
||||||
|
mpd_options_(mpd_options),
|
||||||
|
start_number_(1) {}
|
||||||
|
|
||||||
Representation::~Representation() {}
|
Representation::~Representation() {}
|
||||||
|
|
||||||
|
@ -458,6 +511,9 @@ void Representation::AddNewSegment(uint64 start_time,
|
||||||
|
|
||||||
bandwidth_estimator_.AddBlock(
|
bandwidth_estimator_.AddBlock(
|
||||||
size, static_cast<double>(duration) / media_info_.reference_time_scale());
|
size, static_cast<double>(duration) / media_info_.reference_time_scale());
|
||||||
|
|
||||||
|
SlideWindow();
|
||||||
|
DCHECK_GE(segment_infos_.size(), 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uses info in |media_info_| and |content_protection_elements_| to create a
|
// Uses info in |media_info_| and |content_protection_elements_| to create a
|
||||||
|
@ -516,7 +572,8 @@ xml::ScopedXmlPtr<xmlNode>::type Representation::GetXml() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasLiveOnlyFields(media_info_) &&
|
if (HasLiveOnlyFields(media_info_) &&
|
||||||
!representation.AddLiveOnlyInfo(media_info_, segment_infos_)) {
|
!representation.AddLiveOnlyInfo(
|
||||||
|
media_info_, segment_infos_, start_number_)) {
|
||||||
LOG(ERROR) << "Failed to add Live info.";
|
LOG(ERROR) << "Failed to add Live info.";
|
||||||
return xml::ScopedXmlPtr<xmlNode>::type();
|
return xml::ScopedXmlPtr<xmlNode>::type();
|
||||||
}
|
}
|
||||||
|
@ -599,6 +656,57 @@ bool Representation::IsContiguous(uint64 start_time,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Representation::SlideWindow() {
|
||||||
|
DCHECK(!segment_infos_.empty());
|
||||||
|
if (mpd_options_.time_shift_buffer_depth <= 0.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const uint32 time_scale = GetTimeScale(media_info_);
|
||||||
|
DCHECK_GT(time_scale, 0u);
|
||||||
|
|
||||||
|
uint64 time_shift_buffer_depth =
|
||||||
|
static_cast<uint64>(mpd_options_.time_shift_buffer_depth * time_scale);
|
||||||
|
|
||||||
|
// The start time of the latest segment is considered the current_play_time,
|
||||||
|
// and this should guarantee that the latest segment will stay in the list.
|
||||||
|
const uint64 current_play_time = LatestSegmentStartTime(segment_infos_);
|
||||||
|
if (current_play_time <= time_shift_buffer_depth)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const uint64 timeshift_limit = current_play_time - time_shift_buffer_depth;
|
||||||
|
|
||||||
|
// First remove all the SegmentInfos that are completely out of range, by
|
||||||
|
// looking at the very last segment's end time.
|
||||||
|
std::list<SegmentInfo>::iterator first = segment_infos_.begin();
|
||||||
|
std::list<SegmentInfo>::iterator last = first;
|
||||||
|
size_t num_segments_removed = 0;
|
||||||
|
for (; last != segment_infos_.end(); ++last) {
|
||||||
|
const uint64 last_segment_end_time = LastSegmentEndTime(*last);
|
||||||
|
if (timeshift_limit < last_segment_end_time)
|
||||||
|
break;
|
||||||
|
num_segments_removed += last->repeat + 1;
|
||||||
|
}
|
||||||
|
segment_infos_.erase(first, last);
|
||||||
|
start_number_ += num_segments_removed;
|
||||||
|
|
||||||
|
// Now some segment in the first SegmentInfo should be left in the list.
|
||||||
|
SegmentInfo* first_segment_info = &segment_infos_.front();
|
||||||
|
DCHECK_LE(timeshift_limit, LastSegmentEndTime(*first_segment_info));
|
||||||
|
|
||||||
|
// Identify which segments should still be in the SegmentInfo.
|
||||||
|
const int repeat_index =
|
||||||
|
SearchTimedOutRepeatIndex(timeshift_limit, *first_segment_info);
|
||||||
|
CHECK_GE(repeat_index, 0);
|
||||||
|
if (repeat_index == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
first_segment_info->start_time = first_segment_info->start_time +
|
||||||
|
first_segment_info->duration * repeat_index;
|
||||||
|
|
||||||
|
first_segment_info->repeat = first_segment_info->repeat - repeat_index;
|
||||||
|
start_number_ += repeat_index;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Representation::GetVideoMimeType() const {
|
std::string Representation::GetVideoMimeType() const {
|
||||||
return GetMimeType("video", media_info_.container_type());
|
return GetMimeType("video", media_info_.container_type());
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,11 @@
|
||||||
|
|
||||||
#include "base/atomic_sequence_num.h"
|
#include "base/atomic_sequence_num.h"
|
||||||
#include "base/basictypes.h"
|
#include "base/basictypes.h"
|
||||||
|
#include "base/gtest_prod_util.h"
|
||||||
#include "base/stl_util.h"
|
#include "base/stl_util.h"
|
||||||
#include "base/synchronization/lock.h"
|
#include "base/synchronization/lock.h"
|
||||||
#include "mpd/base/content_protection_element.h"
|
|
||||||
#include "mpd/base/bandwidth_estimator.h"
|
#include "mpd/base/bandwidth_estimator.h"
|
||||||
|
#include "mpd/base/content_protection_element.h"
|
||||||
#include "mpd/base/media_info.pb.h"
|
#include "mpd/base/media_info.pb.h"
|
||||||
#include "mpd/base/mpd_utils.h"
|
#include "mpd/base/mpd_utils.h"
|
||||||
#include "mpd/base/segment_info.h"
|
#include "mpd/base/segment_info.h"
|
||||||
|
@ -105,13 +106,13 @@ class MpdBuilder {
|
||||||
|
|
||||||
float GetStaticMpdDuration(xml::XmlNode* mpd_node);
|
float GetStaticMpdDuration(xml::XmlNode* mpd_node);
|
||||||
|
|
||||||
// Use |options_| to set attributes for MPD. Only values that are set will be
|
// Use |mpd_options_| to set attributes for MPD. Only values that are set will be
|
||||||
// used, i.e. if a string field is not empty and numeric field is not 0.
|
// used, i.e. if a string field is not empty and numeric field is not 0.
|
||||||
// Required fields will be set with some reasonable values.
|
// Required fields will be set with some reasonable values.
|
||||||
void SetMpdOptionsValues(xml::XmlNode* mpd_node);
|
void SetMpdOptionsValues(xml::XmlNode* mpd_node);
|
||||||
|
|
||||||
MpdType type_;
|
MpdType type_;
|
||||||
MpdOptions options_;
|
MpdOptions mpd_options_;
|
||||||
std::list<AdaptationSet*> adaptation_sets_;
|
std::list<AdaptationSet*> adaptation_sets_;
|
||||||
::STLElementDeleter<std::list<AdaptationSet*> > adaptation_sets_deleter_;
|
::STLElementDeleter<std::list<AdaptationSet*> > adaptation_sets_deleter_;
|
||||||
|
|
||||||
|
@ -128,11 +129,6 @@ class MpdBuilder {
|
||||||
/// <ContentProtection> elements to the AdaptationSet element.
|
/// <ContentProtection> elements to the AdaptationSet element.
|
||||||
class AdaptationSet {
|
class AdaptationSet {
|
||||||
public:
|
public:
|
||||||
/// @param adaptation_set_id is an ID number for this AdaptationSet.
|
|
||||||
/// @param representation_counter is a Counter for assigning ID numbers to
|
|
||||||
/// Representation. It can not be NULL.
|
|
||||||
AdaptationSet(uint32 adaptation_set_id,
|
|
||||||
base::AtomicSequenceNumber* representation_counter);
|
|
||||||
~AdaptationSet();
|
~AdaptationSet();
|
||||||
|
|
||||||
/// Create a Representation instance using @a media_info.
|
/// Create a Representation instance using @a media_info.
|
||||||
|
@ -161,6 +157,16 @@ class AdaptationSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class MpdBuilder;
|
||||||
|
FRIEND_TEST_ALL_PREFIXES(StaticMpdBuilderTest, CheckAdaptationSetId);
|
||||||
|
|
||||||
|
/// @param adaptation_set_id is an ID number for this AdaptationSet.
|
||||||
|
/// @param representation_counter is a Counter for assigning ID numbers to
|
||||||
|
/// Representation. It can not be NULL.
|
||||||
|
AdaptationSet(uint32 adaptation_set_id,
|
||||||
|
const MpdOptions& mpd_options_,
|
||||||
|
base::AtomicSequenceNumber* representation_counter);
|
||||||
|
|
||||||
std::list<ContentProtectionElement> content_protection_elements_;
|
std::list<ContentProtectionElement> content_protection_elements_;
|
||||||
std::list<Representation*> representations_;
|
std::list<Representation*> representations_;
|
||||||
::STLElementDeleter<std::list<Representation*> > representations_deleter_;
|
::STLElementDeleter<std::list<Representation*> > representations_deleter_;
|
||||||
|
@ -170,6 +176,7 @@ class AdaptationSet {
|
||||||
base::AtomicSequenceNumber* const representation_counter_;
|
base::AtomicSequenceNumber* const representation_counter_;
|
||||||
|
|
||||||
const uint32 id_;
|
const uint32 id_;
|
||||||
|
const MpdOptions& mpd_options_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(AdaptationSet);
|
DISALLOW_COPY_AND_ASSIGN(AdaptationSet);
|
||||||
};
|
};
|
||||||
|
@ -178,14 +185,6 @@ class AdaptationSet {
|
||||||
/// well as optional ContentProtection elements for that stream.
|
/// well as optional ContentProtection elements for that stream.
|
||||||
class Representation {
|
class Representation {
|
||||||
public:
|
public:
|
||||||
// TODO(rkuroiwa): Get the value from MpdOptions for constructing
|
|
||||||
// BandwidthEstimator.
|
|
||||||
/// @param media_info is a MediaInfo containing information on the media.
|
|
||||||
/// @a media_info.bandwidth is required for 'static' profile. If @a
|
|
||||||
/// media_info.bandwidth is not present in 'dynamic' profile, this
|
|
||||||
/// tries to estimate it using the info passed to AddNewSegment().
|
|
||||||
/// @param representation_id is the numeric ID for the <Representation>.
|
|
||||||
Representation(const MediaInfo& media_info, uint32 representation_id);
|
|
||||||
~Representation();
|
~Representation();
|
||||||
|
|
||||||
/// Tries to initialize the instance. If this does not succeed, the instance
|
/// Tries to initialize the instance. If this does not succeed, the instance
|
||||||
|
@ -217,6 +216,18 @@ class Representation {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class AdaptationSet;
|
||||||
|
FRIEND_TEST_ALL_PREFIXES(StaticMpdBuilderTest, CheckRepresentationId);
|
||||||
|
|
||||||
|
/// @param media_info is a MediaInfo containing information on the media.
|
||||||
|
/// @a media_info.bandwidth is required for 'static' profile. If @a
|
||||||
|
/// media_info.bandwidth is not present in 'dynamic' profile, this
|
||||||
|
/// tries to estimate it using the info passed to AddNewSegment().
|
||||||
|
/// @param representation_id is the numeric ID for the <Representation>.
|
||||||
|
Representation(const MediaInfo& media_info,
|
||||||
|
const MpdOptions& mpd_options,
|
||||||
|
uint32 representation_id);
|
||||||
|
|
||||||
bool AddLiveInfo(xml::RepresentationXmlNode* representation);
|
bool AddLiveInfo(xml::RepresentationXmlNode* representation);
|
||||||
|
|
||||||
// Returns true if |media_info_| has required fields to generate a valid
|
// Returns true if |media_info_| has required fields to generate a valid
|
||||||
|
@ -227,6 +238,11 @@ class Representation {
|
||||||
// segment is contiguous.
|
// segment is contiguous.
|
||||||
bool IsContiguous(uint64 start_time, uint64 duration, uint64 size) const;
|
bool IsContiguous(uint64 start_time, uint64 duration, uint64 size) const;
|
||||||
|
|
||||||
|
// Remove elements from |segment_infos_| if
|
||||||
|
// mpd_options_.time_shift_buffer_depth is specified. Increments
|
||||||
|
// |start_number_| by the number of segments removed.
|
||||||
|
void SlideWindow();
|
||||||
|
|
||||||
// Note: Because 'mimeType' is a required field for a valid MPD, these return
|
// Note: Because 'mimeType' is a required field for a valid MPD, these return
|
||||||
// strings.
|
// strings.
|
||||||
std::string GetVideoMimeType() const;
|
std::string GetVideoMimeType() const;
|
||||||
|
@ -242,6 +258,11 @@ class Representation {
|
||||||
std::string mime_type_;
|
std::string mime_type_;
|
||||||
std::string codecs_;
|
std::string codecs_;
|
||||||
BandwidthEstimator bandwidth_estimator_;
|
BandwidthEstimator bandwidth_estimator_;
|
||||||
|
const MpdOptions& mpd_options_;;
|
||||||
|
|
||||||
|
// startNumber attribute for SegmentTemplate.
|
||||||
|
// Starts from 1.
|
||||||
|
uint32 start_number_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(Representation);
|
DISALLOW_COPY_AND_ASSIGN(Representation);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "base/file_util.h"
|
#include "base/file_util.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/strings/string_number_conversions.h"
|
#include "base/strings/string_number_conversions.h"
|
||||||
|
#include "base/strings/string_util.h"
|
||||||
#include "base/strings/stringprintf.h"
|
#include "base/strings/stringprintf.h"
|
||||||
#include "mpd/base/mpd_builder.h"
|
#include "mpd/base/mpd_builder.h"
|
||||||
#include "mpd/base/mpd_utils.h"
|
#include "mpd/base/mpd_utils.h"
|
||||||
|
@ -18,6 +19,10 @@
|
||||||
namespace dash_packager {
|
namespace dash_packager {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
const char kSElementTemplate[] = "<S t=\"%lu\" d=\"%lu\" r=\"%lu\"/>\n";
|
||||||
|
const char kSElementTemplateWithoutR[] = "<S t=\"%lu\" d=\"%lu\"/>\n";
|
||||||
|
const int kDefaultStartNumber = 1;
|
||||||
|
|
||||||
// Get 'id' attribute from |node|, convert it to std::string and convert it to a
|
// Get 'id' attribute from |node|, convert it to std::string and convert it to a
|
||||||
// number.
|
// number.
|
||||||
void ExpectXmlElementIdEqual(xmlNodePtr node, uint32 id) {
|
void ExpectXmlElementIdEqual(xmlNodePtr node, uint32 id) {
|
||||||
|
@ -90,9 +95,11 @@ class DynamicMpdBuilderTest : public MpdBuilderTest<MpdBuilder::kDynamic> {
|
||||||
// Anchors availabilityStartTime so that the test result doesn't depend on the
|
// Anchors availabilityStartTime so that the test result doesn't depend on the
|
||||||
// current time.
|
// current time.
|
||||||
virtual void SetUp() {
|
virtual void SetUp() {
|
||||||
mpd_.options_.availability_start_time = "2011-12-25T12:30:00";
|
mpd_.mpd_options_.availability_start_time = "2011-12-25T12:30:00";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; }
|
||||||
|
|
||||||
std::string GetDefaultMediaInfo() {
|
std::string GetDefaultMediaInfo() {
|
||||||
const char kMediaInfo[] =
|
const char kMediaInfo[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
|
@ -109,6 +116,7 @@ class DynamicMpdBuilderTest : public MpdBuilderTest<MpdBuilder::kDynamic> {
|
||||||
return base::StringPrintf(kMediaInfo, DefaultTimeScale());
|
return base::StringPrintf(kMediaInfo, DefaultTimeScale());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(rkuroiwa): Make this a global constant in anonymous namespace.
|
||||||
uint64 DefaultTimeScale() const { return 1000; };
|
uint64 DefaultTimeScale() const { return 1000; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,8 +136,6 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest {
|
||||||
uint64 size,
|
uint64 size,
|
||||||
uint64 repeat) {
|
uint64 repeat) {
|
||||||
DCHECK(representation_);
|
DCHECK(representation_);
|
||||||
const char kSElementTemplate[] = "<S t=\"%lu\" d=\"%lu\" r=\"%lu\"/>\n";
|
|
||||||
const char kSElementTemplateWithoutR[] = "<S t=\"%lu\" d=\"%lu\"/>\n";
|
|
||||||
|
|
||||||
SegmentInfo s = {start_time, duration, repeat};
|
SegmentInfo s = {start_time, duration, repeat};
|
||||||
segment_infos_for_expected_out_.push_back(s);
|
segment_infos_for_expected_out_.push_back(s);
|
||||||
|
@ -155,8 +161,8 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest {
|
||||||
AddRepresentation(ConvertToMediaInfo(GetDefaultMediaInfo())));
|
AddRepresentation(ConvertToMediaInfo(GetDefaultMediaInfo())));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TemplateOutputInsertSElementsAndBandwidth(
|
std::string TemplateOutputInsertValues(const std::string& s_elements_string,
|
||||||
const std::string& s_elements_string, uint64 bandwidth) {
|
uint64 bandwidth) {
|
||||||
const char kOutputTemplate[] =
|
const char kOutputTemplate[] =
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" "
|
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" "
|
||||||
|
@ -180,8 +186,9 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest {
|
||||||
" </Period>\n"
|
" </Period>\n"
|
||||||
"</MPD>\n";
|
"</MPD>\n";
|
||||||
|
|
||||||
return base::StringPrintf(
|
return base::StringPrintf(kOutputTemplate,
|
||||||
kOutputTemplate, bandwidth, s_elements_string.data());
|
bandwidth,
|
||||||
|
s_elements_string.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckMpdAgainstExpectedResult() {
|
void CheckMpdAgainstExpectedResult() {
|
||||||
|
@ -189,22 +196,98 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest {
|
||||||
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
|
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
|
||||||
ASSERT_TRUE(ValidateMpdSchema(mpd_doc));
|
ASSERT_TRUE(ValidateMpdSchema(mpd_doc));
|
||||||
const std::string& expected_output =
|
const std::string& expected_output =
|
||||||
TemplateOutputInsertSElementsAndBandwidth(
|
TemplateOutputInsertValues(expected_s_elements_,
|
||||||
expected_s_elements_, bandwidth_estimator_.Estimate());
|
bandwidth_estimator_.Estimate());
|
||||||
ASSERT_TRUE(XmlEqual(expected_output, mpd_doc));
|
ASSERT_TRUE(XmlEqual(expected_output, mpd_doc))
|
||||||
|
<< "Expected " << expected_output << std::endl << "Actual: " << mpd_doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
std::list<SegmentInfo> segment_infos_for_expected_out_;
|
std::list<SegmentInfo> segment_infos_for_expected_out_;
|
||||||
std::string expected_s_elements_;
|
std::string expected_s_elements_;
|
||||||
BandwidthEstimator bandwidth_estimator_;
|
BandwidthEstimator bandwidth_estimator_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TimeShiftBufferDepthTest : public SegmentTemplateTest {
|
||||||
|
public:
|
||||||
|
TimeShiftBufferDepthTest() {}
|
||||||
|
virtual ~TimeShiftBufferDepthTest() {}
|
||||||
|
|
||||||
|
// This function is tricky. It does not call SegmentTemplateTest::Setup() so
|
||||||
|
// that it does not automatically add a representation, that has $Time$
|
||||||
|
// template.
|
||||||
|
virtual void SetUp() {
|
||||||
|
DynamicMpdBuilderTest::SetUp();
|
||||||
|
|
||||||
|
// The only diff with current GetDefaultMediaInfo() is that this uses
|
||||||
|
// $Number$ for segment template.
|
||||||
|
const char kMediaInfo[] =
|
||||||
|
"video_info {\n"
|
||||||
|
" codec: \"avc1.010101\"\n"
|
||||||
|
" width: 720\n"
|
||||||
|
" height: 480\n"
|
||||||
|
" time_scale: 10\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: %lu\n"
|
||||||
|
"container_type: 1\n"
|
||||||
|
"init_segment_name: \"init.mp4\"\n"
|
||||||
|
"segment_template: \"$Number$.mp4\"\n";
|
||||||
|
|
||||||
|
const std::string& number_template_media_info =
|
||||||
|
base::StringPrintf(kMediaInfo, DefaultTimeScale());
|
||||||
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
|
AddRepresentation(ConvertToMediaInfo(number_template_media_info)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckTimeShiftBufferDepthResult(const std::string& expected_s_element,
|
||||||
|
int expected_time_shift_buffer_depth,
|
||||||
|
int expected_start_number) {
|
||||||
|
const char kOutputTemplate[] =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
|
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" "
|
||||||
|
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
|
||||||
|
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
|
||||||
|
"xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\" "
|
||||||
|
"availabilityStartTime=\"2011-12-25T12:30:00\" minBufferTime=\"PT2S\" "
|
||||||
|
"type=\"dynamic\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
|
||||||
|
"timeShiftBufferDepth=\"PT%dS\">\n"
|
||||||
|
" <Period start=\"PT0S\">\n"
|
||||||
|
" <AdaptationSet id=\"0\">\n"
|
||||||
|
" <Representation id=\"0\" bandwidth=\"%lu\" "
|
||||||
|
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
|
||||||
|
"height=\"480\">\n"
|
||||||
|
" <SegmentTemplate timescale=\"1000\" "
|
||||||
|
"initialization=\"init.mp4\" media=\"$Number$.mp4\" "
|
||||||
|
"startNumber=\"%d\">\n"
|
||||||
|
" <SegmentTimeline>\n"
|
||||||
|
" %s\n"
|
||||||
|
" </SegmentTimeline>\n"
|
||||||
|
" </SegmentTemplate>\n"
|
||||||
|
" </Representation>\n"
|
||||||
|
" </AdaptationSet>\n"
|
||||||
|
" </Period>\n"
|
||||||
|
"</MPD>\n";
|
||||||
|
|
||||||
|
std::string expected_out =
|
||||||
|
base::StringPrintf(kOutputTemplate,
|
||||||
|
expected_time_shift_buffer_depth,
|
||||||
|
bandwidth_estimator_.Estimate(),
|
||||||
|
expected_start_number,
|
||||||
|
expected_s_element.c_str());
|
||||||
|
|
||||||
|
std::string mpd_doc;
|
||||||
|
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
|
||||||
|
ASSERT_TRUE(ValidateMpdSchema(mpd_doc));
|
||||||
|
ASSERT_TRUE(XmlEqual(expected_out, mpd_doc))
|
||||||
|
<< "Expected " << expected_out << std::endl << "Actual: " << mpd_doc;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
TEST_F(StaticMpdBuilderTest, CheckAdaptationSetId) {
|
TEST_F(StaticMpdBuilderTest, CheckAdaptationSetId) {
|
||||||
base::AtomicSequenceNumber sequence_counter;
|
base::AtomicSequenceNumber sequence_counter;
|
||||||
const uint32 kAdaptationSetId = 42;
|
const uint32 kAdaptationSetId = 42;
|
||||||
|
|
||||||
AdaptationSet adaptation_set(kAdaptationSetId, &sequence_counter);
|
AdaptationSet adaptation_set(
|
||||||
|
kAdaptationSetId, MpdOptions(), &sequence_counter);
|
||||||
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kAdaptationSetId, &adaptation_set));
|
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kAdaptationSetId, &adaptation_set));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +295,8 @@ TEST_F(StaticMpdBuilderTest, CheckRepresentationId) {
|
||||||
const MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
|
const MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
|
||||||
const uint32 kRepresentationId = 1;
|
const uint32 kRepresentationId = 1;
|
||||||
|
|
||||||
Representation representation(video_media_info, kRepresentationId);
|
Representation representation(
|
||||||
|
video_media_info, MpdOptions(), kRepresentationId);
|
||||||
EXPECT_TRUE(representation.Init());
|
EXPECT_TRUE(representation.Init());
|
||||||
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kRepresentationId, &representation));
|
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kRepresentationId, &representation));
|
||||||
}
|
}
|
||||||
|
@ -421,4 +505,286 @@ TEST_F(SegmentTemplateTest, OverlappingSegmentsWithinErrorRange) {
|
||||||
ASSERT_NO_FATAL_FAILURE(CheckMpdAgainstExpectedResult());
|
ASSERT_NO_FATAL_FAILURE(CheckMpdAgainstExpectedResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All segments have the same duration and size.
|
||||||
|
TEST_F(TimeShiftBufferDepthTest, Normal) {
|
||||||
|
const int kTimeShiftBufferDepth = 10; // 10 sec.
|
||||||
|
mutable_mpd_options()->time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
const uint64 kInitialStartTime = 0;
|
||||||
|
// Trick to make every segment 1 second long.
|
||||||
|
const uint64 kDuration = DefaultTimeScale();
|
||||||
|
const uint64 kSize = 10000;
|
||||||
|
const uint64 kRepeat = 1234;
|
||||||
|
const uint64 kLength = kRepeat;
|
||||||
|
|
||||||
|
CHECK_EQ(kDuration / DefaultTimeScale() * kRepeat, kLength);
|
||||||
|
|
||||||
|
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||||
|
|
||||||
|
// There should only be the last 11 segments because timeshift is 10 sec and
|
||||||
|
// each segment is 1 sec and the latest segments start time is "current
|
||||||
|
// time" i.e., the latest segment does not count as part of timeshift buffer
|
||||||
|
// depth.
|
||||||
|
// Also note that S@r + 1 is the actual number of segments.
|
||||||
|
const int kExpectedRepeatsLeft = kTimeShiftBufferDepth;
|
||||||
|
const std::string expected_s_element =
|
||||||
|
base::StringPrintf(kSElementTemplate,
|
||||||
|
kDuration * (kRepeat - kExpectedRepeatsLeft),
|
||||||
|
kDuration,
|
||||||
|
static_cast<uint64>(kExpectedRepeatsLeft));
|
||||||
|
|
||||||
|
const int kExpectedStartNumber = kRepeat - kExpectedRepeatsLeft + 1;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||||
|
expected_s_element, kTimeShiftBufferDepth, kExpectedStartNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeShiftBufferDepth is shorter than a segment. This should not discard the
|
||||||
|
// segment that can play TimeShiftBufferDepth.
|
||||||
|
// For example if TimeShiftBufferDepth = 1 min. and a 10 min segment was just
|
||||||
|
// added. Before that 9 min segment was added. The 9 min segment should not be
|
||||||
|
// removed from the MPD.
|
||||||
|
TEST_F(TimeShiftBufferDepthTest, TimeShiftBufferDepthShorterThanSegmentLength) {
|
||||||
|
const int kTimeShiftBufferDepth = 10; // 10 sec.
|
||||||
|
mutable_mpd_options()->time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
const uint64 kInitialStartTime = 0;
|
||||||
|
// Each duration is a second longer than timeShiftBufferDepth.
|
||||||
|
const uint64 kDuration = DefaultTimeScale() * (kTimeShiftBufferDepth + 1);
|
||||||
|
const uint64 kSize = 10000;
|
||||||
|
const uint64 kRepeat = 1;
|
||||||
|
|
||||||
|
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||||
|
|
||||||
|
// The two segments should be both present.
|
||||||
|
const std::string expected_s_element = base::StringPrintf(
|
||||||
|
kSElementTemplate, kInitialStartTime, kDuration, kRepeat);
|
||||||
|
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||||
|
expected_s_element, kTimeShiftBufferDepth, kDefaultStartNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
// More generic version the normal test.
|
||||||
|
TEST_F(TimeShiftBufferDepthTest, Generic) {
|
||||||
|
const int kTimeShiftBufferDepth = 30;
|
||||||
|
mutable_mpd_options()->time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
const uint64 kInitialStartTime = 123;
|
||||||
|
const uint64 kDuration = DefaultTimeScale();
|
||||||
|
const uint64 kSize = 10000;
|
||||||
|
const uint64 kRepeat = 1000;
|
||||||
|
|
||||||
|
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||||
|
const uint64 first_s_element_end_time =
|
||||||
|
kInitialStartTime + kDuration * (kRepeat + 1);
|
||||||
|
|
||||||
|
// Now add 2 kTimeShiftBufferDepth long segments.
|
||||||
|
const int kNumMoreSegments = 2;
|
||||||
|
const int kMoreSegmentsRepeat = kNumMoreSegments - 1;
|
||||||
|
const uint64 kTimeShiftBufferDepthDuration =
|
||||||
|
DefaultTimeScale() * kTimeShiftBufferDepth;
|
||||||
|
AddSegments(first_s_element_end_time,
|
||||||
|
kTimeShiftBufferDepthDuration,
|
||||||
|
kSize,
|
||||||
|
kMoreSegmentsRepeat);
|
||||||
|
|
||||||
|
// Expect only the latest S element with 2 segments.
|
||||||
|
const std::string expected_s_element =
|
||||||
|
base::StringPrintf(kSElementTemplate,
|
||||||
|
first_s_element_end_time,
|
||||||
|
kTimeShiftBufferDepthDuration,
|
||||||
|
static_cast<uint64>(kMoreSegmentsRepeat));
|
||||||
|
|
||||||
|
const int kExpectedRemovedSegments = kRepeat + 1;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||||
|
expected_s_element,
|
||||||
|
kTimeShiftBufferDepth,
|
||||||
|
kDefaultStartNumber + kExpectedRemovedSegments));
|
||||||
|
}
|
||||||
|
|
||||||
|
// More than 1 S element in the result.
|
||||||
|
// Adds 100 one-second segments. Then add 21 two-second segments.
|
||||||
|
// This should have all of the two-second segments and 60 one-second
|
||||||
|
// segments. Note that it expects 60 segments from the first S element because
|
||||||
|
// the most recent segment added does not count
|
||||||
|
TEST_F(TimeShiftBufferDepthTest, MoreThanOneS) {
|
||||||
|
const int kTimeShiftBufferDepth = 100;
|
||||||
|
mutable_mpd_options()->time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
const uint64 kInitialStartTime = 0;
|
||||||
|
const uint64 kSize = 20000;
|
||||||
|
|
||||||
|
const uint64 kOneSecondDuration = DefaultTimeScale();
|
||||||
|
const uint64 kOneSecondSegmentRepeat = 99;
|
||||||
|
AddSegments(
|
||||||
|
kInitialStartTime, kOneSecondDuration, kSize, kOneSecondSegmentRepeat);
|
||||||
|
const uint64 first_s_element_end_time =
|
||||||
|
kInitialStartTime + kOneSecondDuration * (kOneSecondSegmentRepeat + 1);
|
||||||
|
|
||||||
|
const uint64 kTwoSecondDuration = 2 * DefaultTimeScale();
|
||||||
|
const uint64 kTwoSecondSegmentRepeat = 20;
|
||||||
|
AddSegments(first_s_element_end_time,
|
||||||
|
kTwoSecondDuration,
|
||||||
|
kSize,
|
||||||
|
kTwoSecondSegmentRepeat);
|
||||||
|
|
||||||
|
const uint64 kExpectedRemovedSegments =
|
||||||
|
(kOneSecondSegmentRepeat + 1 + kTwoSecondSegmentRepeat * 2) -
|
||||||
|
kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
std::string expected_s_element =
|
||||||
|
base::StringPrintf(kSElementTemplate,
|
||||||
|
kOneSecondDuration * kExpectedRemovedSegments,
|
||||||
|
kOneSecondDuration,
|
||||||
|
kOneSecondSegmentRepeat - kExpectedRemovedSegments);
|
||||||
|
expected_s_element += base::StringPrintf(kSElementTemplate,
|
||||||
|
first_s_element_end_time,
|
||||||
|
kTwoSecondDuration,
|
||||||
|
kTwoSecondSegmentRepeat);
|
||||||
|
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||||
|
expected_s_element,
|
||||||
|
kTimeShiftBufferDepth,
|
||||||
|
kDefaultStartNumber + kExpectedRemovedSegments));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Edge case where the last segment in S element should still be in the MPD.
|
||||||
|
// Example:
|
||||||
|
// Assuming timescale = 1 so that duration of 1 means 1 second.
|
||||||
|
// TimeShiftBufferDepth is 9 sec and we currently have
|
||||||
|
// <S t=0 d=1.5 r=1 />
|
||||||
|
// <S t=3 d=2 r=3 />
|
||||||
|
// and we add another contiguous 2 second segment.
|
||||||
|
// Then the first S element's last segment should still be in the MPD.
|
||||||
|
TEST_F(TimeShiftBufferDepthTest, UseLastSegmentInS) {
|
||||||
|
const int kTimeShiftBufferDepth = 9;
|
||||||
|
mutable_mpd_options()->time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
const uint64 kInitialStartTime = 1;
|
||||||
|
const uint64 kDuration1 =
|
||||||
|
static_cast<uint64>(DefaultTimeScale() * 1.5);
|
||||||
|
const uint64 kSize = 20000;
|
||||||
|
const uint64 kRepeat1 = 1;
|
||||||
|
|
||||||
|
AddSegments(kInitialStartTime, kDuration1, kSize, kRepeat1);
|
||||||
|
|
||||||
|
const uint64 first_s_element_end_time =
|
||||||
|
kInitialStartTime + kDuration1 * (kRepeat1 + 1);
|
||||||
|
|
||||||
|
const uint64 kTwoSecondDuration = 2 * DefaultTimeScale();
|
||||||
|
const uint64 kTwoSecondSegmentRepeat = 4;
|
||||||
|
|
||||||
|
AddSegments(first_s_element_end_time,
|
||||||
|
kTwoSecondDuration,
|
||||||
|
kSize,
|
||||||
|
kTwoSecondSegmentRepeat);
|
||||||
|
|
||||||
|
std::string expected_s_element = base::StringPrintf(
|
||||||
|
kSElementTemplateWithoutR,
|
||||||
|
kInitialStartTime + kDuration1, // Expect one segment removed.
|
||||||
|
kDuration1);
|
||||||
|
|
||||||
|
expected_s_element += base::StringPrintf(kSElementTemplate,
|
||||||
|
first_s_element_end_time,
|
||||||
|
kTwoSecondDuration,
|
||||||
|
kTwoSecondSegmentRepeat);
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||||
|
expected_s_element, kTimeShiftBufferDepth, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gap between S elements but both should be included.
|
||||||
|
TEST_F(TimeShiftBufferDepthTest, NormalGap) {
|
||||||
|
const int kTimeShiftBufferDepth = 10;
|
||||||
|
mutable_mpd_options()->time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
const uint64 kInitialStartTime = 0;
|
||||||
|
const uint64 kDuration = DefaultTimeScale();
|
||||||
|
const uint64 kSize = 20000;
|
||||||
|
const uint64 kRepeat = 6;
|
||||||
|
// CHECK here so that the when next S element is added with 1 segment, this S
|
||||||
|
// element doesn't go away.
|
||||||
|
CHECK_LT(kRepeat - 1u, static_cast<uint64>(kTimeShiftBufferDepth));
|
||||||
|
CHECK_EQ(kDuration, DefaultTimeScale());
|
||||||
|
|
||||||
|
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||||
|
|
||||||
|
const uint64 first_s_element_end_time =
|
||||||
|
kInitialStartTime + kDuration * (kRepeat + 1);
|
||||||
|
|
||||||
|
const uint64 gap_s_element_start_time = first_s_element_end_time + 1;
|
||||||
|
AddSegments(gap_s_element_start_time, kDuration, kSize, /* no repeat */ 0);
|
||||||
|
|
||||||
|
std::string expected_s_element = base::StringPrintf(
|
||||||
|
kSElementTemplate, kInitialStartTime, kDuration, kRepeat);
|
||||||
|
expected_s_element += base::StringPrintf(
|
||||||
|
kSElementTemplateWithoutR, gap_s_element_start_time, kDuration);
|
||||||
|
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||||
|
expected_s_element, kTimeShiftBufferDepth, kDefaultStartNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case where there is a huge gap so the first S element is removed.
|
||||||
|
TEST_F(TimeShiftBufferDepthTest, HugeGap) {
|
||||||
|
const int kTimeShiftBufferDepth = 10;
|
||||||
|
mutable_mpd_options()->time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
const uint64 kInitialStartTime = 0;
|
||||||
|
const uint64 kDuration = DefaultTimeScale();
|
||||||
|
const uint64 kSize = 20000;
|
||||||
|
const uint64 kRepeat = 6;
|
||||||
|
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||||
|
|
||||||
|
const uint64 first_s_element_end_time =
|
||||||
|
kInitialStartTime + kDuration * (kRepeat + 1);
|
||||||
|
|
||||||
|
// Big enough gap so first S element should not be there.
|
||||||
|
const uint64 gap_s_element_start_time =
|
||||||
|
first_s_element_end_time +
|
||||||
|
(kTimeShiftBufferDepth + 1) * DefaultTimeScale();
|
||||||
|
const uint64 kSecondSElementRepeat = 9;
|
||||||
|
COMPILE_ASSERT(
|
||||||
|
kSecondSElementRepeat < static_cast<uint64>(kTimeShiftBufferDepth),
|
||||||
|
second_s_element_repeat_must_be_less_than_time_shift_buffer_depth);
|
||||||
|
AddSegments(gap_s_element_start_time, kDuration, kSize, kSecondSElementRepeat);
|
||||||
|
|
||||||
|
std::string expected_s_element = base::StringPrintf(kSElementTemplate,
|
||||||
|
gap_s_element_start_time,
|
||||||
|
kDuration,
|
||||||
|
kSecondSElementRepeat);
|
||||||
|
const int kExpectedRemovedSegments = kRepeat + 1;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||||
|
expected_s_element,
|
||||||
|
kTimeShiftBufferDepth,
|
||||||
|
kDefaultStartNumber + kExpectedRemovedSegments));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if startNumber is working correctly.
|
||||||
|
TEST_F(TimeShiftBufferDepthTest, ManySegments) {
|
||||||
|
const int kTimeShiftBufferDepth = 1;
|
||||||
|
mutable_mpd_options()->time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
const uint64 kInitialStartTime = 0;
|
||||||
|
const uint64 kDuration = DefaultTimeScale();
|
||||||
|
const uint64 kSize = 20000;
|
||||||
|
const uint64 kRepeat = 10000;
|
||||||
|
const uint64 kTotalNumSegments = kRepeat + 1;
|
||||||
|
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||||
|
|
||||||
|
const int kExpectedSegmentsLeft = kTimeShiftBufferDepth + 1;
|
||||||
|
const int kExpectedSegmentsRepeat = kExpectedSegmentsLeft - 1;
|
||||||
|
const int kExpectedRemovedSegments =
|
||||||
|
kTotalNumSegments - kExpectedSegmentsLeft;
|
||||||
|
|
||||||
|
std::string expected_s_element =
|
||||||
|
base::StringPrintf(kSElementTemplate,
|
||||||
|
kExpectedRemovedSegments * kDuration,
|
||||||
|
kDuration,
|
||||||
|
static_cast<uint64>(kExpectedSegmentsRepeat));
|
||||||
|
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||||
|
expected_s_element,
|
||||||
|
kTimeShiftBufferDepth,
|
||||||
|
kDefaultStartNumber + kExpectedRemovedSegments));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dash_packager
|
} // namespace dash_packager
|
||||||
|
|
|
@ -412,7 +412,8 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) {
|
||||||
|
|
||||||
bool RepresentationXmlNode::AddLiveOnlyInfo(
|
bool RepresentationXmlNode::AddLiveOnlyInfo(
|
||||||
const MediaInfo& media_info,
|
const MediaInfo& media_info,
|
||||||
const std::list<SegmentInfo>& segment_infos) {
|
const std::list<SegmentInfo>& segment_infos,
|
||||||
|
uint32 start_number) {
|
||||||
XmlNode segment_template("SegmentTemplate");
|
XmlNode segment_template("SegmentTemplate");
|
||||||
if (media_info.has_reference_time_scale()) {
|
if (media_info.has_reference_time_scale()) {
|
||||||
segment_template.SetIntegerAttribute("timescale",
|
segment_template.SetIntegerAttribute("timescale",
|
||||||
|
@ -431,14 +432,21 @@ bool RepresentationXmlNode::AddLiveOnlyInfo(
|
||||||
"SegmentTemplate@initialization";
|
"SegmentTemplate@initialization";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
segment_template.SetStringAttribute("initialization",
|
segment_template.SetStringAttribute("initialization",
|
||||||
media_info.init_segment_name());
|
media_info.init_segment_name());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media_info.has_segment_template())
|
if (media_info.has_segment_template()) {
|
||||||
segment_template.SetStringAttribute("media", media_info.segment_template());
|
segment_template.SetStringAttribute("media", media_info.segment_template());
|
||||||
|
|
||||||
|
// TODO(rkuroiwa): Need a better check. $$Number is legitimate but not a
|
||||||
|
// template.
|
||||||
|
if (media_info.segment_template().find("$Number") != std::string::npos) {
|
||||||
|
DCHECK_GE(start_number, 1u);
|
||||||
|
segment_template.SetIntegerAttribute("startNumber", start_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(rkuroiwa): Find out when a live MPD doesn't require SegmentTimeline.
|
// TODO(rkuroiwa): Find out when a live MPD doesn't require SegmentTimeline.
|
||||||
XmlNode segment_timeline("SegmentTimeline");
|
XmlNode segment_timeline("SegmentTimeline");
|
||||||
|
|
||||||
|
|
|
@ -152,8 +152,11 @@ class RepresentationXmlNode : public RepresentationBaseXmlNode {
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
bool AddVODOnlyInfo(const MediaInfo& media_info);
|
bool AddVODOnlyInfo(const MediaInfo& media_info);
|
||||||
|
|
||||||
|
/// @param segment_infos is a set of SegmentInfos. This method assumes that
|
||||||
|
/// SegmentInfos are sorted by its start time.
|
||||||
bool AddLiveOnlyInfo(const MediaInfo& media_info,
|
bool AddLiveOnlyInfo(const MediaInfo& media_info,
|
||||||
const std::list<SegmentInfo>& segment_infos);
|
const std::list<SegmentInfo>& segment_infos,
|
||||||
|
uint32 start_number);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Add AudioChannelConfiguration elements. This will add multiple
|
// Add AudioChannelConfiguration elements. This will add multiple
|
||||||
|
|
|
@ -201,19 +201,22 @@ TEST_F(RepresentationTest, AddContentProtectionXml) {
|
||||||
// Some template names cannot be used for init segment name.
|
// Some template names cannot be used for init segment name.
|
||||||
TEST_F(RepresentationTest, InvalidLiveInitSegmentName) {
|
TEST_F(RepresentationTest, InvalidLiveInitSegmentName) {
|
||||||
MediaInfo media_info;
|
MediaInfo media_info;
|
||||||
|
const uint32 kDefaultStartNumber = 1;
|
||||||
|
|
||||||
// $NUMBER$ cannot be used for segment name.
|
// $Number$ cannot be used for segment name.
|
||||||
media_info.set_init_segment_name("$Number$.mp4");
|
media_info.set_init_segment_name("$Number$.mp4");
|
||||||
|
ASSERT_FALSE(representation_.AddLiveOnlyInfo(
|
||||||
|
media_info, segment_infos_, kDefaultStartNumber));
|
||||||
|
|
||||||
ASSERT_FALSE(representation_.AddLiveOnlyInfo(media_info, segment_infos_));
|
// $Time$ as well.
|
||||||
|
|
||||||
// $TIME$ as well.
|
|
||||||
media_info.set_init_segment_name("$Time$.mp4");
|
media_info.set_init_segment_name("$Time$.mp4");
|
||||||
ASSERT_FALSE(representation_.AddLiveOnlyInfo(media_info, segment_infos_));
|
ASSERT_FALSE(representation_.AddLiveOnlyInfo(
|
||||||
|
media_info, segment_infos_, kDefaultStartNumber));
|
||||||
|
|
||||||
// This should be valid.
|
// This should be valid.
|
||||||
media_info.set_init_segment_name("some_non_template_name.mp4");
|
media_info.set_init_segment_name("some_non_template_name.mp4");
|
||||||
ASSERT_TRUE(representation_.AddLiveOnlyInfo(media_info, segment_infos_));
|
ASSERT_TRUE(representation_.AddLiveOnlyInfo(
|
||||||
|
media_info, segment_infos_, kDefaultStartNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace xml
|
} // namespace xml
|
||||||
|
|
Loading…
Reference in New Issue