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:
Rintaro Kuroiwa 2015-07-13 10:44:52 -07:00
parent 8d84ebfed7
commit fe851f692b
5 changed files with 489 additions and 42 deletions

View File

@ -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 {

View File

@ -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);
};

View File

@ -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,

View File

@ -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',

View File

@ -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>