Calculate (sub)SegmentAlignment for AdaptationSets
- AdaptationSet::OnNewSegmentForRepresentation() calculates whether the Representations are aligned by looking at the start time and duration. (Except that duration is not used in this implementation). - Add RepresentationStateChangeListener to callback AdaptationSet::OnNewSegmentForRepresentation() from Representation. Change-Id: I3c30bd6652880dabb9d5c619d8a733ffc789eec9
This commit is contained in:
parent
8d84ebfed7
commit
fe851f692b
|
@ -329,16 +329,39 @@ class LibXmlInitializer {
|
|||
DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
|
||||
};
|
||||
|
||||
class RepresentationStateChangeListenerImpl
|
||||
: public RepresentationStateChangeListener {
|
||||
public:
|
||||
// |adaptation_set| is not owned by this class.
|
||||
RepresentationStateChangeListenerImpl(uint32_t representation_id,
|
||||
AdaptationSet* adaptation_set)
|
||||
: representation_id_(representation_id), adaptation_set_(adaptation_set) {
|
||||
DCHECK(adaptation_set_);
|
||||
}
|
||||
virtual ~RepresentationStateChangeListenerImpl() OVERRIDE {}
|
||||
|
||||
// RepresentationStateChangeListener implementation.
|
||||
virtual void OnNewSegmentForRepresentation(uint64_t start_time,
|
||||
uint64_t duration) OVERRIDE {
|
||||
adaptation_set_->OnNewSegmentForRepresentation(representation_id_,
|
||||
start_time, duration);
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t representation_id_;
|
||||
AdaptationSet* const adaptation_set_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RepresentationStateChangeListenerImpl);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
MpdBuilder::MpdBuilder(MpdType type, const MpdOptions& mpd_options)
|
||||
: type_(type),
|
||||
mpd_options_(mpd_options),
|
||||
adaptation_sets_deleter_(&adaptation_sets_) {
|
||||
}
|
||||
adaptation_sets_deleter_(&adaptation_sets_) {}
|
||||
|
||||
MpdBuilder::~MpdBuilder() {
|
||||
}
|
||||
MpdBuilder::~MpdBuilder() {}
|
||||
|
||||
void MpdBuilder::AddBaseUrl(const std::string& base_url) {
|
||||
base::AutoLock scoped_lock(lock_);
|
||||
|
@ -349,6 +372,7 @@ AdaptationSet* MpdBuilder::AddAdaptationSet(const std::string& lang) {
|
|||
base::AutoLock scoped_lock(lock_);
|
||||
scoped_ptr<AdaptationSet> adaptation_set(
|
||||
new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
|
||||
type_,
|
||||
&representation_counter_));
|
||||
|
||||
DCHECK(adaptation_set);
|
||||
|
@ -592,30 +616,38 @@ void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
|
|||
AdaptationSet::AdaptationSet(uint32_t adaptation_set_id,
|
||||
const std::string& lang,
|
||||
const MpdOptions& mpd_options,
|
||||
MpdBuilder::MpdType mpd_type,
|
||||
base::AtomicSequenceNumber* counter)
|
||||
: representations_deleter_(&representations_),
|
||||
representation_counter_(counter),
|
||||
id_(adaptation_set_id),
|
||||
lang_(lang),
|
||||
mpd_options_(mpd_options),
|
||||
group_(kAdaptationSetGroupNotSet) {
|
||||
mpd_type_(mpd_type),
|
||||
group_(kAdaptationSetGroupNotSet),
|
||||
segments_aligned_(kSegmentAlignmentUnknown),
|
||||
force_set_segment_alignment_(false) {
|
||||
DCHECK(counter);
|
||||
}
|
||||
|
||||
AdaptationSet::~AdaptationSet() {
|
||||
}
|
||||
AdaptationSet::~AdaptationSet() {}
|
||||
|
||||
Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) {
|
||||
base::AutoLock scoped_lock(lock_);
|
||||
const uint32_t representation_id = representation_counter_->GetNext();
|
||||
// Note that AdaptationSet outlive Representation, so this object
|
||||
// will die before AdaptationSet.
|
||||
scoped_ptr<RepresentationStateChangeListener> listener(
|
||||
new RepresentationStateChangeListenerImpl(representation_id, this));
|
||||
scoped_ptr<Representation> representation(new Representation(
|
||||
media_info, mpd_options_, representation_counter_->GetNext()));
|
||||
media_info, mpd_options_, representation_id, listener.Pass()));
|
||||
|
||||
if (!representation->Init())
|
||||
return NULL;
|
||||
|
||||
// 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()) {
|
||||
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());
|
||||
|
@ -699,6 +731,13 @@ xml::ScopedXmlPtr<xmlNode>::type AdaptationSet::GetXml() {
|
|||
video_frame_rates_.rbegin()->second);
|
||||
}
|
||||
|
||||
if (segments_aligned_ == kSegmentAlignmentTrue) {
|
||||
adaptation_set.SetStringAttribute(mpd_type_ == MpdBuilder::kStatic
|
||||
? "subSegmentAlignment"
|
||||
: "segmentAlignment",
|
||||
"true");
|
||||
}
|
||||
|
||||
if (picture_aspect_ratio_.size() == 1)
|
||||
adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin());
|
||||
|
||||
|
@ -713,6 +752,19 @@ xml::ScopedXmlPtr<xmlNode>::type AdaptationSet::GetXml() {
|
|||
return adaptation_set.PassScopedPtr();
|
||||
}
|
||||
|
||||
void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) {
|
||||
segments_aligned_ =
|
||||
segment_alignment ? kSegmentAlignmentTrue : kSegmentAlignmentFalse;
|
||||
force_set_segment_alignment_ = true;
|
||||
}
|
||||
|
||||
void AdaptationSet::OnNewSegmentForRepresentation(uint32_t representation_id,
|
||||
uint64_t start_time,
|
||||
uint64_t duration) {
|
||||
base::AutoLock scoped_lock(lock_);
|
||||
CheckSegmentAlignment(representation_id, start_time, duration);
|
||||
}
|
||||
|
||||
bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) {
|
||||
DCHECK(timestamp_seconds);
|
||||
|
||||
|
@ -734,18 +786,90 @@ bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) {
|
|||
return true;
|
||||
}
|
||||
|
||||
Representation::Representation(const MediaInfo& media_info,
|
||||
const MpdOptions& mpd_options,
|
||||
uint32_t id)
|
||||
// This implementation assumes that each representations' segments' are
|
||||
// contiguous.
|
||||
// Also assumes that all Representations are added before this is called.
|
||||
// This checks whether the first elements of the lists in
|
||||
// representation_segment_start_times_ are aligned.
|
||||
// For example, suppose this method was just called with args rep_id=2
|
||||
// start_time=1.
|
||||
// 1 -> [1, 100, 200]
|
||||
// 2 -> [1]
|
||||
// The timestamps of the first elements match, so this flags
|
||||
// segments_aligned_=true.
|
||||
// Also since the first segment start times match, the first element of all the
|
||||
// lists are removed, so the map of lists becomes:
|
||||
// 1 -> [100, 200]
|
||||
// 2 -> []
|
||||
// Note that there could be false positives.
|
||||
// e.g. just got rep_id=3 start_time=1 duration=300, and the duration of the
|
||||
// whole AdaptationSet is 300.
|
||||
// 1 -> [1, 100, 200]
|
||||
// 2 -> [1, 90, 100]
|
||||
// 3 -> [1]
|
||||
// They are not aligned but this will be marked as aligned.
|
||||
// But since this is unlikely to happen in the packager (and to save
|
||||
// computation), this isn't handled at the moment.
|
||||
// TODO(rkuroiwa): For VOD, not all Representations get added to an
|
||||
// AdaptationSet before this is called. Add a similar but separate method that
|
||||
// keeps the timestamps around. It shouldn't out-of-memory for VOD.
|
||||
void AdaptationSet::CheckSegmentAlignment(uint32_t representation_id,
|
||||
uint64_t start_time,
|
||||
uint64_t /* duration */) {
|
||||
if (segments_aligned_ == kSegmentAlignmentFalse ||
|
||||
force_set_segment_alignment_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::list<uint64_t>& representation_start_times =
|
||||
representation_segment_start_times_[representation_id];
|
||||
representation_start_times.push_back(start_time);
|
||||
// There's no way to detemine whether the segments are aligned if some
|
||||
// representations do not have any segments.
|
||||
if (representation_segment_start_times_.size() != representations_.size())
|
||||
return;
|
||||
|
||||
DCHECK(!representation_start_times.empty());
|
||||
const uint64_t expected_start_time = representation_start_times.front();
|
||||
for (RepresentationTimeline::const_iterator it =
|
||||
representation_segment_start_times_.begin();
|
||||
it != representation_segment_start_times_.end(); ++it) {
|
||||
// If there are no entries in a list, then there is no way for the
|
||||
// segment alignment status to change.
|
||||
// Note that it can be empty because entries get deleted below.
|
||||
if (it->second.empty())
|
||||
return;
|
||||
|
||||
if (expected_start_time != it->second.front()) {
|
||||
// Flag as false and clear the start times data, no need to keep it
|
||||
// around.
|
||||
segments_aligned_ = kSegmentAlignmentFalse;
|
||||
representation_segment_start_times_.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
segments_aligned_ = kSegmentAlignmentTrue;
|
||||
|
||||
for (RepresentationTimeline::iterator it =
|
||||
representation_segment_start_times_.begin();
|
||||
it != representation_segment_start_times_.end(); ++it) {
|
||||
it->second.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
Representation::Representation(
|
||||
const MediaInfo& media_info,
|
||||
const MpdOptions& mpd_options,
|
||||
uint32_t id,
|
||||
scoped_ptr<RepresentationStateChangeListener> state_change_listener)
|
||||
: media_info_(media_info),
|
||||
id_(id),
|
||||
bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks),
|
||||
mpd_options_(mpd_options),
|
||||
start_number_(1) {
|
||||
}
|
||||
start_number_(1),
|
||||
state_change_listener_(state_change_listener.Pass()) {}
|
||||
|
||||
Representation::~Representation() {
|
||||
}
|
||||
Representation::~Representation() {}
|
||||
|
||||
bool Representation::Init() {
|
||||
codecs_ = GetCodecs(media_info_);
|
||||
|
@ -801,6 +925,8 @@ void Representation::AddNewSegment(uint64_t start_time,
|
|||
}
|
||||
|
||||
base::AutoLock scoped_lock(lock_);
|
||||
if (state_change_listener_)
|
||||
state_change_listener_->OnNewSegmentForRepresentation(start_time, duration);
|
||||
if (IsContiguous(start_time, duration, size)) {
|
||||
++segment_infos_.back().repeat;
|
||||
} else {
|
||||
|
|
|
@ -200,6 +200,13 @@ class AdaptationSet {
|
|||
/// NULL ScopedXmlPtr.
|
||||
xml::ScopedXmlPtr<xmlNode>::type GetXml();
|
||||
|
||||
/// Forces the (sub)segmentAlignment field to be set to @a segment_alignment.
|
||||
/// Use this if you are certain that the (sub)segments are alinged/unaligned
|
||||
/// for the AdaptationSet.
|
||||
/// @param segment_alignment is the value used for (sub)segmentAlignment
|
||||
/// attribute.
|
||||
void ForceSetSegmentAlignment(bool segment_alignment);
|
||||
|
||||
/// Sets the AdaptationSet@group attribute.
|
||||
/// Passing a negative value to this method will unset the attribute.
|
||||
/// Note that group=0 is a special group, as mentioned in the DASH MPD
|
||||
|
@ -212,7 +219,43 @@ class AdaptationSet {
|
|||
// Must be unique in the Period.
|
||||
uint32_t id() const { return id_; }
|
||||
|
||||
/// Notifies the AdaptationSet instance that a new (sub)segment was added to
|
||||
/// the Representation with @a representation_id.
|
||||
/// This must be called every time a (sub)segment is added to a
|
||||
/// Representation in this AdaptationSet.
|
||||
/// If a Representation is constructed using AddRepresentation() this
|
||||
/// is called automatically whenever Representation::AddNewSegment() is
|
||||
/// is called.
|
||||
/// @param representation_id is the id of the Representation with a new
|
||||
/// segment.
|
||||
/// @param start_time is the start time of the new segment.
|
||||
/// @param duration is the duration of the new segment.
|
||||
void OnNewSegmentForRepresentation(uint32_t representation_id,
|
||||
uint64_t start_time,
|
||||
uint64_t duration);
|
||||
|
||||
private:
|
||||
// kSegmentAlignmentUnknown means that it is uncertain if the
|
||||
// (sub)segments are aligned or not.
|
||||
// kSegmentAlignmentTrue means that it is certain that the all the (current)
|
||||
// segments added to the adaptation set are aligned.
|
||||
// kSegmentAlignmentFalse means that it is it is certain that some segments
|
||||
// are not aligned. This is useful to disable the computation for
|
||||
// segment alignment, once it is certain that some segments are not aligned.
|
||||
enum SegmentAligmentStatus {
|
||||
kSegmentAlignmentUnknown,
|
||||
kSegmentAlignmentTrue,
|
||||
kSegmentAlignmentFalse
|
||||
};
|
||||
|
||||
// This maps Representations (IDs) to a list of start times of the segments.
|
||||
// e.g.
|
||||
// If Representation 1 has start time 0, 100, 200 and Representation 2 has
|
||||
// start times 0, 200, 400, then the map contains:
|
||||
// 1 -> [0, 100, 200]
|
||||
// 2 -> [0, 200, 400]
|
||||
typedef std::map<uint32_t, std::list<uint64_t> > RepresentationTimeline;
|
||||
|
||||
friend class MpdBuilder;
|
||||
FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckAdaptationSetId);
|
||||
FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest,
|
||||
|
@ -222,19 +265,37 @@ class AdaptationSet {
|
|||
FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest,
|
||||
CheckAdaptationSetTextContentType);
|
||||
FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, SetAdaptationSetGroup);
|
||||
FRIEND_TEST_ALL_PREFIXES(StaticMpdBuilderTest, SubSegmentAlignment);
|
||||
FRIEND_TEST_ALL_PREFIXES(StaticMpdBuilderTest, ForceSetSubSegmentAlignment);
|
||||
FRIEND_TEST_ALL_PREFIXES(DynamicMpdBuilderTest, SegmentAlignment);
|
||||
|
||||
/// @param adaptation_set_id is an ID number for this AdaptationSet.
|
||||
/// @param lang is the language of this AdaptationSet. Mainly relevant for
|
||||
/// audio.
|
||||
/// @param mpd_options is the options for this MPD.
|
||||
/// @param mpd_type is the type of this MPD.
|
||||
/// @param representation_counter is a Counter for assigning ID numbers to
|
||||
/// Representation. It can not be NULL.
|
||||
AdaptationSet(uint32_t adaptation_set_id,
|
||||
const std::string& lang,
|
||||
const MpdOptions& mpd_options,
|
||||
MpdBuilder::MpdType mpd_type,
|
||||
base::AtomicSequenceNumber* representation_counter);
|
||||
|
||||
// Gets the earliest, normalized segment timestamp. Returns true if
|
||||
// successful, false otherwise.
|
||||
bool GetEarliestTimestamp(double* timestamp_seconds);
|
||||
|
||||
/// Called from OnNewSegmentForRepresentation(). Checks whether the segments
|
||||
/// are aligned. Sets segments_aligned_.
|
||||
/// @param representation_id is the id of the Representation with a new
|
||||
/// segment.
|
||||
/// @param start_time is the start time of the new segment.
|
||||
/// @param duration is the duration of the new segment.
|
||||
void CheckSegmentAlignment(uint32_t representation_id,
|
||||
uint64_t start_time,
|
||||
uint64_t duration);
|
||||
|
||||
std::list<ContentProtectionElement> content_protection_elements_;
|
||||
std::list<Representation*> representations_;
|
||||
::STLElementDeleter<std::list<Representation*> > representations_deleter_;
|
||||
|
@ -246,6 +307,7 @@ class AdaptationSet {
|
|||
const uint32_t id_;
|
||||
const std::string lang_;
|
||||
const MpdOptions& mpd_options_;
|
||||
const MpdBuilder::MpdType mpd_type_;
|
||||
|
||||
// The group attribute for the AdaptationSet. If the value is negative,
|
||||
// no group number is specified.
|
||||
|
@ -283,9 +345,31 @@ class AdaptationSet {
|
|||
// The roles of this AdaptationSet.
|
||||
std::set<Role> roles_;
|
||||
|
||||
// True iff all the segments are aligned.
|
||||
SegmentAligmentStatus segments_aligned_;
|
||||
bool force_set_segment_alignment_;
|
||||
|
||||
// Keeps track of segment start times of Representations.
|
||||
RepresentationTimeline representation_segment_start_times_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AdaptationSet);
|
||||
};
|
||||
|
||||
// TODO(rkuroiwa): OnSetSampleDuration() must also be added to this to notify
|
||||
// sample duration change to AdaptationSet, to set the right frame rate.
|
||||
class RepresentationStateChangeListener {
|
||||
public:
|
||||
RepresentationStateChangeListener() {}
|
||||
virtual ~RepresentationStateChangeListener() {}
|
||||
|
||||
/// Notifies the instance that a new (sub)segment was added to
|
||||
/// the Representation.
|
||||
/// @param start_time is the start time of the new segment.
|
||||
/// @param duration is the duration of the new segment.
|
||||
virtual void OnNewSegmentForRepresentation(uint64_t start_time,
|
||||
uint64_t duration) = 0;
|
||||
};
|
||||
|
||||
/// Representation class contains references to a single media stream, as
|
||||
/// well as optional ContentProtection elements for that stream.
|
||||
class Representation {
|
||||
|
@ -309,8 +393,10 @@ class Representation {
|
|||
/// then the former is used.
|
||||
void AddContentProtectionElement(const ContentProtectionElement& element);
|
||||
|
||||
/// Add a media segment to the representation.
|
||||
/// @param start_time is the start time for the segment, in units of the
|
||||
/// Add a media (sub)segment to the representation.
|
||||
/// AdaptationSet@{subSegmentAlignment,segmentAlignment} cannot be set
|
||||
/// if this is not called for all Representations.
|
||||
/// @param start_time is the start time for the (sub)segment, in units of the
|
||||
/// stream's time scale.
|
||||
/// @param duration is the duration of the segment, in units of the stream's
|
||||
/// time scale.
|
||||
|
@ -339,15 +425,22 @@ class Representation {
|
|||
FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml);
|
||||
FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckRepresentationId);
|
||||
FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, SetSampleDuration);
|
||||
FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest,
|
||||
RepresentationStateChangeListener);
|
||||
|
||||
/// @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 mpd_options is options for the entire MPD.
|
||||
/// @param representation_id is the numeric ID for the <Representation>.
|
||||
Representation(const MediaInfo& media_info,
|
||||
const MpdOptions& mpd_options,
|
||||
uint32_t representation_id);
|
||||
/// @param state_change_listener is an event handler for state changes to
|
||||
/// the representation. If null, no event handler registered.
|
||||
Representation(
|
||||
const MediaInfo& media_info,
|
||||
const MpdOptions& mpd_options,
|
||||
uint32_t representation_id,
|
||||
scoped_ptr<RepresentationStateChangeListener> state_change_listener);
|
||||
|
||||
bool AddLiveInfo(xml::RepresentationXmlNode* representation);
|
||||
|
||||
|
@ -391,6 +484,10 @@ class Representation {
|
|||
// Starts from 1.
|
||||
uint32_t start_number_;
|
||||
|
||||
// If this is not null, then Representation is responsible for calling the
|
||||
// right methods at right timings.
|
||||
scoped_ptr<RepresentationStateChangeListener> state_change_listener_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Representation);
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <inttypes.h>
|
||||
#include <libxml/xmlstring.h>
|
||||
|
@ -77,6 +78,16 @@ void ExpectAttributeNotSet(base::StringPiece attribute, xmlNodePtr node) {
|
|||
xmlGetProp(node, BAD_CAST attribute.data()));
|
||||
ASSERT_FALSE(attribute_xml_str);
|
||||
}
|
||||
|
||||
class MockRepresentationStateChangeListener
|
||||
: public RepresentationStateChangeListener {
|
||||
public:
|
||||
MockRepresentationStateChangeListener() {}
|
||||
~MockRepresentationStateChangeListener() {}
|
||||
|
||||
MOCK_METHOD2(OnNewSegmentForRepresentation,
|
||||
void(uint64_t start_time, uint64_t duration));
|
||||
};
|
||||
} // namespace
|
||||
|
||||
template <MpdBuilder::MpdType type>
|
||||
|
@ -106,6 +117,12 @@ class MpdBuilderTest: public ::testing::Test {
|
|||
representation_ = representation;
|
||||
}
|
||||
|
||||
// Helper function to return an empty listener for tests that don't need
|
||||
// it.
|
||||
scoped_ptr<RepresentationStateChangeListener> NoListener() {
|
||||
return scoped_ptr<RepresentationStateChangeListener>();
|
||||
}
|
||||
|
||||
MpdBuilder mpd_;
|
||||
|
||||
// We usually need only one representation.
|
||||
|
@ -199,6 +216,8 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest {
|
|||
|
||||
std::string TemplateOutputInsertValues(const std::string& s_elements_string,
|
||||
uint64_t bandwidth) {
|
||||
// Note: Since all the tests have 1 Representation, the AdaptationSet
|
||||
// always has segmentAligntment=true.
|
||||
const char kOutputTemplate[] =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" "
|
||||
|
@ -210,7 +229,7 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest {
|
|||
" <Period start=\"PT0S\">\n"
|
||||
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
|
||||
" frameRate=\"10/5\" contentType=\"video\""
|
||||
" par=\"3:2\">\n"
|
||||
" par=\"3:2\" segmentAlignment=\"true\">\n"
|
||||
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
|
||||
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
|
||||
"height=\"480\" frameRate=\"10/5\" sar=\"1:1\">\n"
|
||||
|
@ -282,6 +301,8 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest {
|
|||
void CheckTimeShiftBufferDepthResult(const std::string& expected_s_element,
|
||||
int expected_time_shift_buffer_depth,
|
||||
int expected_start_number) {
|
||||
// Note: Since all the tests have 1 Representation, the AdaptationSet
|
||||
// always has segmentAligntment=true.
|
||||
const char kOutputTemplate[] =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" "
|
||||
|
@ -294,7 +315,7 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest {
|
|||
" <Period start=\"PT0S\">\n"
|
||||
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
|
||||
" frameRate=\"10/2\" contentType=\"video\""
|
||||
" par=\"3:2\">\n"
|
||||
" par=\"3:2\" segmentAlignment=\"true\">\n"
|
||||
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
|
||||
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
|
||||
"height=\"480\" frameRate=\"10/2\" sar=\"1:1\">\n"
|
||||
|
@ -328,8 +349,8 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest {
|
|||
// Verify that AdaptationSet@group can be set and unset.
|
||||
TEST_F(CommonMpdBuilderTest, SetAdaptationSetGroup) {
|
||||
base::AtomicSequenceNumber sequence_counter;
|
||||
AdaptationSet adaptation_set(
|
||||
kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter);
|
||||
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set.set_group(1);
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type xml_with_group(adaptation_set.GetXml());
|
||||
|
@ -357,8 +378,9 @@ TEST_F(CommonMpdBuilderTest, ValidMediaInfo) {
|
|||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
Representation representation(
|
||||
ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId);
|
||||
Representation representation(ConvertToMediaInfo(kTestMediaInfo),
|
||||
MpdOptions(), kAnyRepresentationId,
|
||||
NoListener());
|
||||
EXPECT_TRUE(representation.Init());
|
||||
}
|
||||
|
||||
|
@ -375,8 +397,9 @@ TEST_F(CommonMpdBuilderTest, InvalidMediaInfo) {
|
|||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
Representation representation(
|
||||
ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId);
|
||||
Representation representation(ConvertToMediaInfo(kTestMediaInfo),
|
||||
MpdOptions(), kAnyRepresentationId,
|
||||
NoListener());
|
||||
EXPECT_FALSE(representation.Init());
|
||||
}
|
||||
|
||||
|
@ -393,8 +416,9 @@ TEST_F(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml) {
|
|||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
Representation representation(
|
||||
ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId);
|
||||
Representation representation(ConvertToMediaInfo(kTestMediaInfo),
|
||||
MpdOptions(), kAnyRepresentationId,
|
||||
NoListener());
|
||||
EXPECT_TRUE(representation.Init());
|
||||
xml::ScopedXmlPtr<xmlNode>::type node_xml(representation.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
|
@ -409,6 +433,35 @@ TEST_F(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml) {
|
|||
ExpectAttributeEqString("frameRate", "10/10", node_xml.get()));
|
||||
}
|
||||
|
||||
// Make sure RepresentationStateChangeListener::OnNewSegmentForRepresentation()
|
||||
// is called.
|
||||
TEST_F(CommonMpdBuilderTest, RepresentationStateChangeListener) {
|
||||
const char kTestMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
const uint64_t kStartTime = 199238u;
|
||||
const uint64_t kDuration = 98u;
|
||||
scoped_ptr<MockRepresentationStateChangeListener> listener(
|
||||
new MockRepresentationStateChangeListener());
|
||||
EXPECT_CALL(*listener,
|
||||
OnNewSegmentForRepresentation(kStartTime, kDuration));
|
||||
Representation representation(
|
||||
ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId,
|
||||
listener.PassAs<RepresentationStateChangeListener>());
|
||||
EXPECT_TRUE(representation.Init());
|
||||
|
||||
representation.AddNewSegment(kStartTime, kDuration, 10 /* any size */);
|
||||
}
|
||||
|
||||
// Verify that content type is set correctly if video info is present in
|
||||
// MediaInfo.
|
||||
TEST_F(CommonMpdBuilderTest, CheckAdaptationSetVideoContentType) {
|
||||
|
@ -425,8 +478,8 @@ TEST_F(CommonMpdBuilderTest, CheckAdaptationSetVideoContentType) {
|
|||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
AdaptationSet adaptation_set(
|
||||
kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter);
|
||||
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo));
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set.GetXml());
|
||||
|
@ -447,8 +500,8 @@ TEST_F(CommonMpdBuilderTest, CheckAdaptationSetAudioContentType) {
|
|||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
AdaptationSet adaptation_set(
|
||||
kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter);
|
||||
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo));
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set.GetXml());
|
||||
|
@ -470,8 +523,8 @@ TEST_F(CommonMpdBuilderTest, DISABLED_CheckAdaptationSetTextContentType) {
|
|||
"}\n"
|
||||
"container_type: 1\n";
|
||||
|
||||
AdaptationSet adaptation_set(
|
||||
kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter);
|
||||
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(kTextMediaInfo));
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set.GetXml());
|
||||
|
@ -482,8 +535,8 @@ TEST_F(CommonMpdBuilderTest, DISABLED_CheckAdaptationSetTextContentType) {
|
|||
TEST_F(CommonMpdBuilderTest, CheckAdaptationSetId) {
|
||||
base::AtomicSequenceNumber sequence_counter;
|
||||
const uint32_t kAdaptationSetId = 42;
|
||||
AdaptationSet adaptation_set(
|
||||
kAdaptationSetId, "", MpdOptions(), &sequence_counter);
|
||||
AdaptationSet adaptation_set(kAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kAdaptationSetId, &adaptation_set));
|
||||
}
|
||||
|
||||
|
@ -749,6 +802,176 @@ TEST_F(CommonMpdBuilderTest,
|
|||
ExpectAttributeNotSet("frameRate", adaptation_set_xml.get()));
|
||||
}
|
||||
|
||||
// Verify that subSegmentAlignment is set to true if all the Representations'
|
||||
// segments are aligned and the MPD type is static.
|
||||
TEST_F(StaticMpdBuilderTest, SubSegmentAlignment) {
|
||||
base::AtomicSequenceNumber sequence_counter;
|
||||
const char k480pMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 8\n"
|
||||
" pixel_height: 9\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
const char k360pMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 640\n"
|
||||
" height: 360\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
Representation* representation_480p =
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
|
||||
Representation* representation_360p =
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
|
||||
|
||||
// First use same start time and duration, and verify that that
|
||||
// segmentAlignment is set.
|
||||
const uint64_t kStartTime = 0u;
|
||||
const uint64_t kDuration = 10u;
|
||||
const uint64_t kAnySize = 19834u;
|
||||
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize);
|
||||
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
|
||||
xml::ScopedXmlPtr<xmlNode>::type aligned(adaptation_set.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeEqString("subSegmentAlignment", "true", aligned.get()));
|
||||
EXPECT_EQ(2u, adaptation_set.representation_segment_start_times_.size());
|
||||
|
||||
// Also check that the start times are removed from the vector.
|
||||
EXPECT_EQ(0u,
|
||||
adaptation_set
|
||||
.representation_segment_start_times_[representation_480p->id()]
|
||||
.size());
|
||||
EXPECT_EQ(0u,
|
||||
adaptation_set
|
||||
.representation_segment_start_times_[representation_360p->id()]
|
||||
.size());
|
||||
|
||||
// Add segments that make them not aligned.
|
||||
representation_480p->AddNewSegment(11, 20, kAnySize);
|
||||
representation_360p->AddNewSegment(10, 1, kAnySize);
|
||||
representation_360p->AddNewSegment(11, 19, kAnySize);
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type unaligned(adaptation_set.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeNotSet("subSegmentAlignment", unaligned.get()));
|
||||
}
|
||||
|
||||
// Verify that subSegmentAlignment can be force set to true.
|
||||
TEST_F(StaticMpdBuilderTest, ForceSetSubSegmentAlignment) {
|
||||
base::AtomicSequenceNumber sequence_counter;
|
||||
const char k480pMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 8\n"
|
||||
" pixel_height: 9\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
const char k360pMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 640\n"
|
||||
" height: 360\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
Representation* representation_480p =
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
|
||||
Representation* representation_360p =
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
|
||||
|
||||
// Use different starting times to make the segments "not aligned".
|
||||
const uint64_t kStartTime1 = 1u;
|
||||
const uint64_t kStartTime2 = 2u;
|
||||
COMPILE_ASSERT(kStartTime1 != kStartTime2, StartTimesShouldBeDifferent);
|
||||
const uint64_t kDuration = 10u;
|
||||
const uint64_t kAnySize = 19834u;
|
||||
representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize);
|
||||
representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize);
|
||||
xml::ScopedXmlPtr<xmlNode>::type unaligned(adaptation_set.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeNotSet("subSegmentAlignment", unaligned.get()));
|
||||
|
||||
// Then force set the segment alignment attribute to true.
|
||||
adaptation_set.ForceSetSegmentAlignment(true);
|
||||
xml::ScopedXmlPtr<xmlNode>::type aligned(adaptation_set.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeEqString("subSegmentAlignment", "true", aligned.get()));
|
||||
}
|
||||
|
||||
// Verify that segmentAlignment is set to true if all the Representations
|
||||
// segments' are aligned and the MPD type is dynamic.
|
||||
TEST_F(DynamicMpdBuilderTest, SegmentAlignment) {
|
||||
base::AtomicSequenceNumber sequence_counter;
|
||||
const char k480pMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 8\n"
|
||||
" pixel_height: 9\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
const char k360pMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" width: 640\n"
|
||||
" height: 360\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kDynamic, &sequence_counter);
|
||||
Representation* representation_480p =
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
|
||||
Representation* representation_360p =
|
||||
adaptation_set.AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
|
||||
|
||||
// First use same start time and duration, and verify that that
|
||||
// segmentAlignment is set.
|
||||
const uint64_t kStartTime = 0u;
|
||||
const uint64_t kDuration = 10u;
|
||||
const uint64_t kAnySize = 19834u;
|
||||
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize);
|
||||
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize);
|
||||
xml::ScopedXmlPtr<xmlNode>::type aligned(adaptation_set.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeEqString("segmentAlignment", "true", aligned.get()));
|
||||
|
||||
// Add segments that make them not aligned.
|
||||
representation_480p->AddNewSegment(11, 20, kAnySize);
|
||||
representation_360p->AddNewSegment(10, 1, kAnySize);
|
||||
representation_360p->AddNewSegment(11, 19, kAnySize);
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type unaligned(adaptation_set.GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeNotSet("subSegmentAlignment", unaligned.get()));
|
||||
}
|
||||
|
||||
// Verify that the width and height attribute are set if all the video
|
||||
// representations have the same width and height.
|
||||
TEST_F(StaticMpdBuilderTest, AdapatationSetWidthAndHeight) {
|
||||
|
@ -835,7 +1058,7 @@ TEST_F(CommonMpdBuilderTest, CheckRepresentationId) {
|
|||
const uint32_t kRepresentationId = 1;
|
||||
|
||||
Representation representation(
|
||||
video_media_info, MpdOptions(), kRepresentationId);
|
||||
video_media_info, MpdOptions(), kRepresentationId, NoListener());
|
||||
EXPECT_TRUE(representation.Init());
|
||||
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kRepresentationId, &representation));
|
||||
}
|
||||
|
@ -849,7 +1072,7 @@ TEST_F(CommonMpdBuilderTest, SetSampleDuration) {
|
|||
const uint32_t kRepresentationId = 1;
|
||||
|
||||
Representation representation(
|
||||
video_media_info, MpdOptions(), kRepresentationId);
|
||||
video_media_info, MpdOptions(), kRepresentationId, NoListener());
|
||||
EXPECT_TRUE(representation.Init());
|
||||
representation.SetSampleDuration(2u);
|
||||
EXPECT_EQ(2u,
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
'../base/base.gyp:base',
|
||||
'../media/file/file.gyp:file',
|
||||
'../media/test/media_test.gyp:run_all_unittests',
|
||||
'../testing/gmock.gyp:gmock',
|
||||
'../testing/gtest.gyp:gtest',
|
||||
'mpd_builder',
|
||||
'mpd_util',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<Period start="PT0S">
|
||||
<AdaptationSet id="0" width="720" height="480" frameRate="10/5" contentType="video" par="3:2">
|
||||
<AdaptationSet id="0" width="720" height="480" frameRate="10/5" contentType="video" par="3:2" segmentAlignment="true">
|
||||
<Representation id="0" bandwidth="102400" codecs="avc1.010101" mimeType="video/mp4" width="720" height="480" frameRate="10/5" sar="1:1">
|
||||
<SegmentTemplate timescale="1000" initialization="init.mp4" media="$Time$.mp4">
|
||||
<SegmentTimeline>
|
||||
|
|
Loading…
Reference in New Issue