Move SegmentTemplateTest and TimeShiftBufferDepthTest too
Moved from mpd_unittest.cc to representation_unittest.cc. Also removed unused test data files in packager/mpd/test/data. Change-Id: I23811390b4b1ffbfac3e69c1ffcf92544099a53d
This commit is contained in:
parent
cacdab2209
commit
55edaf0bea
|
@ -4,39 +4,16 @@
|
|||
// 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>
|
||||
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/base/strings/string_piece.h"
|
||||
#include "packager/base/strings/string_util.h"
|
||||
#include "packager/base/strings/stringprintf.h"
|
||||
#include "packager/mpd/base/adaptation_set.h"
|
||||
#include "packager/mpd/base/content_protection_element.h"
|
||||
#include "packager/mpd/base/mpd_builder.h"
|
||||
#include "packager/mpd/base/mpd_utils.h"
|
||||
#include "packager/mpd/base/representation.h"
|
||||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||
#include "packager/mpd/test/mpd_builder_test_helper.h"
|
||||
#include "packager/mpd/test/xml_compare.h"
|
||||
#include "packager/version/version.h"
|
||||
|
||||
namespace shaka {
|
||||
|
||||
using base::FilePath;
|
||||
using ::testing::Not;
|
||||
|
||||
namespace {
|
||||
// Any number for {AdaptationSet,Representation} ID. Required to create
|
||||
// either objects. Not checked in test.
|
||||
const char kSElementTemplate[] =
|
||||
"<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\" r=\"%" PRIu64 "\"/>\n";
|
||||
const char kSElementTemplateWithoutR[] =
|
||||
"<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\"/>\n";
|
||||
const int kDefaultStartNumber = 1;
|
||||
|
||||
class TestClock : public base::Clock {
|
||||
public:
|
||||
|
@ -96,10 +73,6 @@ class MpdBuilderTest : public ::testing::Test {
|
|||
|
||||
class OnDemandMpdBuilderTest : public MpdBuilderTest<DashProfile::kOnDemand> {};
|
||||
|
||||
// Use this test name for things that are common to both static an dynamic
|
||||
// mpd builder tests.
|
||||
typedef OnDemandMpdBuilderTest CommonMpdBuilderTest;
|
||||
|
||||
class LiveMpdBuilderTest : public MpdBuilderTest<DashProfile::kLive> {
|
||||
public:
|
||||
~LiveMpdBuilderTest() override {}
|
||||
|
@ -127,203 +100,6 @@ class LiveMpdBuilderTest : public MpdBuilderTest<DashProfile::kLive> {
|
|||
mpd_.InjectClockForTesting(std::unique_ptr<base::Clock>(
|
||||
new TestClock(base::Time::FromUTCExploded(test_time))));
|
||||
}
|
||||
|
||||
std::string GetDefaultMediaInfo() {
|
||||
const char kMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1.010101'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 5\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"reference_time_scale: %u\n"
|
||||
"container_type: 1\n"
|
||||
"init_segment_name: 'init.mp4'\n"
|
||||
"segment_template: '$Time$.mp4'\n";
|
||||
|
||||
return base::StringPrintf(kMediaInfo, DefaultTimeScale());
|
||||
}
|
||||
|
||||
// TODO(rkuroiwa): Make this a global constant in anonymous namespace.
|
||||
uint32_t DefaultTimeScale() const { return 1000; };
|
||||
};
|
||||
|
||||
class SegmentTemplateTest : public LiveMpdBuilderTest {
|
||||
public:
|
||||
SegmentTemplateTest()
|
||||
: bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks) {}
|
||||
~SegmentTemplateTest() override {}
|
||||
|
||||
void SetUp() override {
|
||||
LiveMpdBuilderTest::SetUp();
|
||||
ASSERT_NO_FATAL_FAILURE(AddRepresentationWithDefaultMediaInfo());
|
||||
}
|
||||
|
||||
void AddSegments(uint64_t start_time,
|
||||
uint64_t duration,
|
||||
uint64_t size,
|
||||
uint64_t repeat) {
|
||||
DCHECK(representation_);
|
||||
|
||||
SegmentInfo s = {start_time, duration, repeat};
|
||||
segment_infos_for_expected_out_.push_back(s);
|
||||
if (repeat == 0) {
|
||||
expected_s_elements_ +=
|
||||
base::StringPrintf(kSElementTemplateWithoutR, start_time, duration);
|
||||
} else {
|
||||
expected_s_elements_ +=
|
||||
base::StringPrintf(kSElementTemplate, start_time, duration, repeat);
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < repeat + 1; ++i) {
|
||||
representation_->AddNewSegment(start_time, duration, size);
|
||||
start_time += duration;
|
||||
bandwidth_estimator_.AddBlock(
|
||||
size, static_cast<double>(duration) / DefaultTimeScale());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void AddRepresentationWithDefaultMediaInfo() {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
AddRepresentation(ConvertToMediaInfo(GetDefaultMediaInfo())));
|
||||
}
|
||||
|
||||
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\" "
|
||||
" 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\" "
|
||||
" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
|
||||
" minBufferTime=\"PT2S\" type=\"dynamic\" "
|
||||
" publishTime=\"2016-01-11T15:10:24Z\">\n"
|
||||
" <Period id=\"0\" start=\"PT0S\">\n"
|
||||
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
|
||||
" frameRate=\"10/5\" contentType=\"video\""
|
||||
" par=\"3:2\" segmentAlignment=\"true\">\n"
|
||||
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
|
||||
" codecs=\"avc1.010101\" mimeType=\"video/mp4\" sar=\"1:1\">\n"
|
||||
" <SegmentTemplate timescale=\"1000\" "
|
||||
"initialization=\"init.mp4\" media=\"$Time$.mp4\">\n"
|
||||
" <SegmentTimeline>\n%s"
|
||||
" </SegmentTimeline>\n"
|
||||
" </SegmentTemplate>\n"
|
||||
" </Representation>\n"
|
||||
" </AdaptationSet>\n"
|
||||
" </Period>\n"
|
||||
"</MPD>\n";
|
||||
|
||||
return base::StringPrintf(kOutputTemplate,
|
||||
bandwidth,
|
||||
s_elements_string.c_str());
|
||||
}
|
||||
|
||||
void CheckMpdAgainstExpectedResult() {
|
||||
std::string mpd_doc;
|
||||
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
|
||||
ASSERT_TRUE(ValidateMpdSchema(mpd_doc));
|
||||
const std::string& expected_output =
|
||||
TemplateOutputInsertValues(expected_s_elements_,
|
||||
bandwidth_estimator_.Estimate());
|
||||
ASSERT_TRUE(XmlEqual(expected_output, mpd_doc))
|
||||
<< "Expected " << expected_output << std::endl << "Actual: " << mpd_doc;
|
||||
}
|
||||
|
||||
std::list<SegmentInfo> segment_infos_for_expected_out_;
|
||||
std::string expected_s_elements_;
|
||||
BandwidthEstimator bandwidth_estimator_;
|
||||
};
|
||||
|
||||
class TimeShiftBufferDepthTest : public SegmentTemplateTest {
|
||||
public:
|
||||
TimeShiftBufferDepthTest() {}
|
||||
~TimeShiftBufferDepthTest() override {}
|
||||
|
||||
// This function is tricky. It does not call SegmentTemplateTest::Setup() so
|
||||
// that it does not automatically add a representation, that has $Time$
|
||||
// template.
|
||||
void SetUp() override {
|
||||
LiveMpdBuilderTest::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"
|
||||
" frame_duration: 2\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"reference_time_scale: %u\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) {
|
||||
// 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\" "
|
||||
"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\" "
|
||||
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
|
||||
"minBufferTime=\"PT2S\" type=\"dynamic\" "
|
||||
"publishTime=\"2016-01-11T15:10:24Z\" "
|
||||
"timeShiftBufferDepth=\"PT%dS\">\n"
|
||||
" <Period id=\"0\" start=\"PT0S\">\n"
|
||||
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
|
||||
" frameRate=\"10/2\" contentType=\"video\""
|
||||
" par=\"3:2\" segmentAlignment=\"true\">\n"
|
||||
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
|
||||
" codecs=\"avc1.010101\" mimeType=\"video/mp4\" sar=\"1:1\">\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;
|
||||
}
|
||||
};
|
||||
|
||||
// Add one video check the output.
|
||||
|
@ -431,421 +207,6 @@ TEST_F(LiveMpdBuilderTest, StaticCheckMpdAttributes) {
|
|||
ASSERT_EQ(kExpectedOutput, mpd_doc);
|
||||
}
|
||||
|
||||
// Estimate the bandwidth given the info from AddNewSegment().
|
||||
TEST_F(SegmentTemplateTest, OneSegmentNormal) {
|
||||
const uint64_t kStartTime = 0;
|
||||
const uint64_t kDuration = 10;
|
||||
const uint64_t kSize = 128;
|
||||
AddSegments(kStartTime, kDuration, kSize, 0);
|
||||
|
||||
// TODO(rkuroiwa): Clean up the test/data directory. It's a mess.
|
||||
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputDynamicNormal));
|
||||
}
|
||||
|
||||
TEST_F(SegmentTemplateTest, NormalRepeatedSegmentDuration) {
|
||||
const uint64_t kSize = 256;
|
||||
uint64_t start_time = 0;
|
||||
uint64_t duration = 40000;
|
||||
uint64_t repeat = 2;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
start_time += duration * (repeat + 1);
|
||||
duration = 54321;
|
||||
repeat = 0;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
start_time += duration * (repeat + 1);
|
||||
duration = 12345;
|
||||
repeat = 0;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(CheckMpdAgainstExpectedResult());
|
||||
}
|
||||
|
||||
TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) {
|
||||
const uint64_t kSize = 100000;
|
||||
uint64_t start_time = 0;
|
||||
uint64_t duration = 100000;
|
||||
uint64_t repeat = 2;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
start_time += duration * (repeat + 1);
|
||||
duration = 20000;
|
||||
repeat = 3;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
start_time += duration * (repeat + 1);
|
||||
duration = 32123;
|
||||
repeat = 3;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(CheckMpdAgainstExpectedResult());
|
||||
}
|
||||
|
||||
// Segments not starting from 0.
|
||||
// Start time is 10. Make sure r gets set correctly.
|
||||
TEST_F(SegmentTemplateTest, NonZeroStartTime) {
|
||||
const uint64_t kStartTime = 10;
|
||||
const uint64_t kDuration = 22000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 1;
|
||||
AddSegments(kStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(CheckMpdAgainstExpectedResult());
|
||||
}
|
||||
|
||||
// There is a gap in the segments, but still valid.
|
||||
TEST_F(SegmentTemplateTest, NonContiguousLiveInfo) {
|
||||
const uint64_t kStartTime = 10;
|
||||
const uint64_t kDuration = 22000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 0;
|
||||
AddSegments(kStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
const uint64_t kStartTimeOffset = 100;
|
||||
AddSegments(kDuration + kStartTimeOffset, kDuration, kSize, kRepeat);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(CheckMpdAgainstExpectedResult());
|
||||
}
|
||||
|
||||
// Add segments out of order. Segments that start before the previous segment
|
||||
// cannot be added.
|
||||
TEST_F(SegmentTemplateTest, OutOfOrder) {
|
||||
const uint64_t kEarlierStartTime = 0;
|
||||
const uint64_t kLaterStartTime = 1000;
|
||||
const uint64_t kDuration = 1000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 0;
|
||||
|
||||
AddSegments(kLaterStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(CheckMpdAgainstExpectedResult());
|
||||
}
|
||||
|
||||
// No segments should be overlapping.
|
||||
TEST_F(SegmentTemplateTest, OverlappingSegments) {
|
||||
const uint64_t kEarlierStartTime = 0;
|
||||
const uint64_t kDuration = 1000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 0;
|
||||
|
||||
const uint64_t kOverlappingSegmentStartTime = kDuration / 2;
|
||||
CHECK_GT(kDuration, kOverlappingSegmentStartTime);
|
||||
|
||||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kOverlappingSegmentStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(CheckMpdAgainstExpectedResult());
|
||||
}
|
||||
|
||||
// Some segments can be overlapped due to rounding errors. As long as it falls
|
||||
// in the range of rounding error defined inside MpdBuilder, the segment gets
|
||||
// accepted.
|
||||
TEST_F(SegmentTemplateTest, OverlappingSegmentsWithinErrorRange) {
|
||||
const uint64_t kEarlierStartTime = 0;
|
||||
const uint64_t kDuration = 1000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 0;
|
||||
|
||||
const uint64_t kOverlappingSegmentStartTime = kDuration - 1;
|
||||
CHECK_GT(kDuration, kOverlappingSegmentStartTime);
|
||||
|
||||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kOverlappingSegmentStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
// Trick to make every segment 1 second long.
|
||||
const uint64_t kDuration = DefaultTimeScale();
|
||||
const uint64_t kSize = 10000;
|
||||
const uint64_t kRepeat = 1234;
|
||||
const uint64_t 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_t>(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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
// Each duration is a second longer than timeShiftBufferDepth.
|
||||
const uint64_t kDuration = DefaultTimeScale() * (kTimeShiftBufferDepth + 1);
|
||||
const uint64_t kSize = 10000;
|
||||
const uint64_t 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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 123;
|
||||
const uint64_t kDuration = DefaultTimeScale();
|
||||
const uint64_t kSize = 10000;
|
||||
const uint64_t kRepeat = 1000;
|
||||
|
||||
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||
const uint64_t 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_t 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_t>(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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
const uint64_t kSize = 20000;
|
||||
|
||||
const uint64_t kOneSecondDuration = DefaultTimeScale();
|
||||
const uint64_t kOneSecondSegmentRepeat = 99;
|
||||
AddSegments(
|
||||
kInitialStartTime, kOneSecondDuration, kSize, kOneSecondSegmentRepeat);
|
||||
const uint64_t first_s_element_end_time =
|
||||
kInitialStartTime + kOneSecondDuration * (kOneSecondSegmentRepeat + 1);
|
||||
|
||||
const uint64_t kTwoSecondDuration = 2 * DefaultTimeScale();
|
||||
const uint64_t kTwoSecondSegmentRepeat = 20;
|
||||
AddSegments(first_s_element_end_time,
|
||||
kTwoSecondDuration,
|
||||
kSize,
|
||||
kTwoSecondSegmentRepeat);
|
||||
|
||||
const uint64_t 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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 1;
|
||||
const uint64_t kDuration1 = static_cast<uint64_t>(DefaultTimeScale() * 1.5);
|
||||
const uint64_t kSize = 20000;
|
||||
const uint64_t kRepeat1 = 1;
|
||||
|
||||
AddSegments(kInitialStartTime, kDuration1, kSize, kRepeat1);
|
||||
|
||||
const uint64_t first_s_element_end_time =
|
||||
kInitialStartTime + kDuration1 * (kRepeat1 + 1);
|
||||
|
||||
const uint64_t kTwoSecondDuration = 2 * DefaultTimeScale();
|
||||
const uint64_t 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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
const uint64_t kDuration = DefaultTimeScale();
|
||||
const uint64_t kSize = 20000;
|
||||
const uint64_t 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_t>(kTimeShiftBufferDepth));
|
||||
CHECK_EQ(kDuration, DefaultTimeScale());
|
||||
|
||||
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
const uint64_t first_s_element_end_time =
|
||||
kInitialStartTime + kDuration * (kRepeat + 1);
|
||||
|
||||
const uint64_t 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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
const uint64_t kDuration = DefaultTimeScale();
|
||||
const uint64_t kSize = 20000;
|
||||
const uint64_t kRepeat = 6;
|
||||
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
const uint64_t first_s_element_end_time =
|
||||
kInitialStartTime + kDuration * (kRepeat + 1);
|
||||
|
||||
// Big enough gap so first S element should not be there.
|
||||
const uint64_t gap_s_element_start_time =
|
||||
first_s_element_end_time +
|
||||
(kTimeShiftBufferDepth + 1) * DefaultTimeScale();
|
||||
const uint64_t kSecondSElementRepeat = 9;
|
||||
static_assert(
|
||||
kSecondSElementRepeat < static_cast<uint64_t>(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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
const uint64_t kDuration = DefaultTimeScale();
|
||||
const uint64_t kSize = 20000;
|
||||
const uint64_t kRepeat = 10000;
|
||||
const uint64_t 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_t>(kExpectedSegmentsRepeat));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(CheckTimeShiftBufferDepthResult(
|
||||
expected_s_element,
|
||||
kTimeShiftBufferDepth,
|
||||
kDefaultStartNumber + kExpectedRemovedSegments));
|
||||
}
|
||||
|
||||
namespace {
|
||||
const char kMediaFile[] = "foo/bar/media.mp4";
|
||||
const char kMediaFileBase[] = "media.mp4";
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "packager/base/strings/stringprintf.h"
|
||||
#include "packager/mpd/test/mpd_builder_test_helper.h"
|
||||
#include "packager/mpd/test/xml_compare.h"
|
||||
|
||||
|
@ -53,7 +55,7 @@ class RepresentationTest : public ::testing::Test {
|
|||
return std::unique_ptr<RepresentationStateChangeListener>();
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
MpdOptions mpd_options_;
|
||||
};
|
||||
|
||||
|
@ -366,4 +368,562 @@ TEST_F(RepresentationTest, CheckRepresentationId) {
|
|||
AttributeEqual("id", std::to_string(kRepresentationId)));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Any number for {AdaptationSet,Representation} ID. Required to create
|
||||
// either objects. Not checked in test.
|
||||
const char kSElementTemplate[] =
|
||||
"<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\" r=\"%" PRIu64 "\"/>\n";
|
||||
const char kSElementTemplateWithoutR[] =
|
||||
"<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\"/>\n";
|
||||
const int kDefaultStartNumber = 1;
|
||||
const uint32_t kDefaultTimeScale = 1000u;
|
||||
|
||||
std::string GetDefaultMediaInfo() {
|
||||
const char kMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1.010101'\n"
|
||||
" width: 720\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 5\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"reference_time_scale: %u\n"
|
||||
"container_type: 1\n"
|
||||
"init_segment_name: 'init.mp4'\n"
|
||||
"segment_template: '$Time$.mp4'\n";
|
||||
|
||||
return base::StringPrintf(kMediaInfo, kDefaultTimeScale);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class SegmentTemplateTest : public RepresentationTest {
|
||||
public:
|
||||
SegmentTemplateTest()
|
||||
: bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks) {}
|
||||
~SegmentTemplateTest() override {}
|
||||
|
||||
void SetUp() override {
|
||||
mpd_options_.mpd_type = MpdType::kDynamic;
|
||||
representation_ =
|
||||
CreateRepresentation(ConvertToMediaInfo(GetDefaultMediaInfo()),
|
||||
kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation_->Init());
|
||||
}
|
||||
|
||||
void AddSegments(uint64_t start_time,
|
||||
uint64_t duration,
|
||||
uint64_t size,
|
||||
uint64_t repeat) {
|
||||
DCHECK(representation_);
|
||||
|
||||
SegmentInfo s = {start_time, duration, repeat};
|
||||
segment_infos_for_expected_out_.push_back(s);
|
||||
if (repeat == 0) {
|
||||
expected_s_elements_ +=
|
||||
base::StringPrintf(kSElementTemplateWithoutR, start_time, duration);
|
||||
} else {
|
||||
expected_s_elements_ +=
|
||||
base::StringPrintf(kSElementTemplate, start_time, duration, repeat);
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < repeat + 1; ++i) {
|
||||
representation_->AddNewSegment(start_time, duration, size);
|
||||
start_time += duration;
|
||||
bandwidth_estimator_.AddBlock(
|
||||
size, static_cast<double>(duration) / kDefaultTimeScale);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string ExpectedXml() {
|
||||
const char kOutputTemplate[] =
|
||||
"<Representation id=\"1\" bandwidth=\"%" PRIu64
|
||||
"\" "
|
||||
" codecs=\"avc1.010101\" mimeType=\"video/mp4\" sar=\"1:1\" "
|
||||
" width=\"720\" height=\"480\" frameRate=\"10/5\">\n"
|
||||
" <SegmentTemplate timescale=\"1000\" "
|
||||
" initialization=\"init.mp4\" media=\"$Time$.mp4\">\n"
|
||||
" <SegmentTimeline>\n"
|
||||
" %s\n"
|
||||
" </SegmentTimeline>\n"
|
||||
" </SegmentTemplate>\n"
|
||||
"</Representation>\n";
|
||||
return base::StringPrintf(kOutputTemplate, bandwidth_estimator_.Estimate(),
|
||||
expected_s_elements_.c_str());
|
||||
}
|
||||
|
||||
std::unique_ptr<Representation> representation_;
|
||||
std::list<SegmentInfo> segment_infos_for_expected_out_;
|
||||
std::string expected_s_elements_;
|
||||
BandwidthEstimator bandwidth_estimator_;
|
||||
};
|
||||
|
||||
// Estimate the bandwidth given the info from AddNewSegment().
|
||||
TEST_F(SegmentTemplateTest, OneSegmentNormal) {
|
||||
const uint64_t kStartTime = 0;
|
||||
const uint64_t kDuration = 10;
|
||||
const uint64_t kSize = 128;
|
||||
AddSegments(kStartTime, kDuration, kSize, 0);
|
||||
|
||||
const char kExpectedXml[] =
|
||||
"<Representation id=\"1\" bandwidth=\"102400\" "
|
||||
" codecs=\"avc1.010101\" mimeType=\"video/mp4\" sar=\"1:1\" "
|
||||
" width=\"720\" height=\"480\" frameRate=\"10/5\">\n"
|
||||
" <SegmentTemplate timescale=\"1000\" "
|
||||
" initialization=\"init.mp4\" media=\"$Time$.mp4\">\n"
|
||||
" <SegmentTimeline>\n"
|
||||
" <S t=\"0\" d=\"10\"/>\n"
|
||||
" </SegmentTimeline>\n"
|
||||
" </SegmentTemplate>\n"
|
||||
"</Representation>\n";
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml));
|
||||
}
|
||||
|
||||
TEST_F(SegmentTemplateTest, NormalRepeatedSegmentDuration) {
|
||||
const uint64_t kSize = 256;
|
||||
uint64_t start_time = 0;
|
||||
uint64_t duration = 40000;
|
||||
uint64_t repeat = 2;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
start_time += duration * (repeat + 1);
|
||||
duration = 54321;
|
||||
repeat = 0;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
start_time += duration * (repeat + 1);
|
||||
duration = 12345;
|
||||
repeat = 0;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
TEST_F(SegmentTemplateTest, RepeatedSegmentsFromNonZeroStartTime) {
|
||||
const uint64_t kSize = 100000;
|
||||
uint64_t start_time = 0;
|
||||
uint64_t duration = 100000;
|
||||
uint64_t repeat = 2;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
start_time += duration * (repeat + 1);
|
||||
duration = 20000;
|
||||
repeat = 3;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
start_time += duration * (repeat + 1);
|
||||
duration = 32123;
|
||||
repeat = 3;
|
||||
AddSegments(start_time, duration, kSize, repeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// Segments not starting from 0.
|
||||
// Start time is 10. Make sure r gets set correctly.
|
||||
TEST_F(SegmentTemplateTest, NonZeroStartTime) {
|
||||
const uint64_t kStartTime = 10;
|
||||
const uint64_t kDuration = 22000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 1;
|
||||
AddSegments(kStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// There is a gap in the segments, but still valid.
|
||||
TEST_F(SegmentTemplateTest, NonContiguousLiveInfo) {
|
||||
const uint64_t kStartTime = 10;
|
||||
const uint64_t kDuration = 22000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 0;
|
||||
AddSegments(kStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
const uint64_t kStartTimeOffset = 100;
|
||||
AddSegments(kDuration + kStartTimeOffset, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// Add segments out of order. Segments that start before the previous segment
|
||||
// cannot be added.
|
||||
TEST_F(SegmentTemplateTest, OutOfOrder) {
|
||||
const uint64_t kEarlierStartTime = 0;
|
||||
const uint64_t kLaterStartTime = 1000;
|
||||
const uint64_t kDuration = 1000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 0;
|
||||
|
||||
AddSegments(kLaterStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// No segments should be overlapping.
|
||||
TEST_F(SegmentTemplateTest, OverlappingSegments) {
|
||||
const uint64_t kEarlierStartTime = 0;
|
||||
const uint64_t kDuration = 1000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 0;
|
||||
|
||||
const uint64_t kOverlappingSegmentStartTime = kDuration / 2;
|
||||
CHECK_GT(kDuration, kOverlappingSegmentStartTime);
|
||||
|
||||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kOverlappingSegmentStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
// Some segments can be overlapped due to rounding errors. As long as it falls
|
||||
// in the range of rounding error defined inside MpdBuilder, the segment gets
|
||||
// accepted.
|
||||
TEST_F(SegmentTemplateTest, OverlappingSegmentsWithinErrorRange) {
|
||||
const uint64_t kEarlierStartTime = 0;
|
||||
const uint64_t kDuration = 1000;
|
||||
const uint64_t kSize = 123456;
|
||||
const uint64_t kRepeat = 0;
|
||||
|
||||
const uint64_t kOverlappingSegmentStartTime = kDuration - 1;
|
||||
CHECK_GT(kDuration, kOverlappingSegmentStartTime);
|
||||
|
||||
AddSegments(kEarlierStartTime, kDuration, kSize, kRepeat);
|
||||
AddSegments(kOverlappingSegmentStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(ExpectedXml()));
|
||||
}
|
||||
|
||||
class TimeShiftBufferDepthTest : public SegmentTemplateTest {
|
||||
public:
|
||||
void SetUp() override {
|
||||
// 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"
|
||||
" frame_duration: 2\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"reference_time_scale: %u\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, kDefaultTimeScale);
|
||||
mpd_options_.mpd_type = MpdType::kDynamic;
|
||||
representation_ =
|
||||
CreateRepresentation(ConvertToMediaInfo(number_template_media_info),
|
||||
kAnyRepresentationId, NoListener());
|
||||
ASSERT_TRUE(representation_->Init());
|
||||
}
|
||||
|
||||
std::string ExpectedXml(const std::string& expected_s_element,
|
||||
int expected_start_number) {
|
||||
const char kOutputTemplate[] =
|
||||
"<Representation id=\"1\" bandwidth=\"%" PRIu64
|
||||
"\" "
|
||||
" codecs=\"avc1.010101\" mimeType=\"video/mp4\" sar=\"1:1\" "
|
||||
" width=\"720\" height=\"480\" frameRate=\"10/2\">\n"
|
||||
" <SegmentTemplate timescale=\"1000\" "
|
||||
" initialization=\"init.mp4\" media=\"$Number$.mp4\" "
|
||||
" startNumber=\"%d\">\n"
|
||||
" <SegmentTimeline>\n"
|
||||
" %s\n"
|
||||
" </SegmentTimeline>\n"
|
||||
" </SegmentTemplate>\n"
|
||||
"</Representation>\n";
|
||||
|
||||
return base::StringPrintf(kOutputTemplate, bandwidth_estimator_.Estimate(),
|
||||
expected_start_number,
|
||||
expected_s_element.c_str());
|
||||
}
|
||||
|
||||
MpdOptions* mutable_mpd_options() { return &mpd_options_; }
|
||||
};
|
||||
|
||||
// All segments have the same duration and size.
|
||||
TEST_F(TimeShiftBufferDepthTest, Normal) {
|
||||
const int kTimeShiftBufferDepth = 10; // 10 sec.
|
||||
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
// Trick to make every segment 1 second long.
|
||||
const uint64_t kDuration = kDefaultTimeScale;
|
||||
const uint64_t kSize = 10000;
|
||||
const uint64_t kRepeat = 1234;
|
||||
const uint64_t kLength = kRepeat;
|
||||
|
||||
CHECK_EQ(kDuration / kDefaultTimeScale * 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_t>(kExpectedRepeatsLeft));
|
||||
|
||||
const int kExpectedStartNumber = kRepeat - kExpectedRepeatsLeft + 1;
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, 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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
// Each duration is a second longer than timeShiftBufferDepth.
|
||||
const uint64_t kDuration = kDefaultTimeScale * (kTimeShiftBufferDepth + 1);
|
||||
const uint64_t kSize = 10000;
|
||||
const uint64_t 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);
|
||||
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, kDefaultStartNumber)));
|
||||
}
|
||||
|
||||
// More generic version the normal test.
|
||||
TEST_F(TimeShiftBufferDepthTest, Generic) {
|
||||
const int kTimeShiftBufferDepth = 30;
|
||||
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 123;
|
||||
const uint64_t kDuration = kDefaultTimeScale;
|
||||
const uint64_t kSize = 10000;
|
||||
const uint64_t kRepeat = 1000;
|
||||
|
||||
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||
const uint64_t 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_t kTimeShiftBufferDepthDuration =
|
||||
kDefaultTimeScale * 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_t>(kMoreSegmentsRepeat));
|
||||
|
||||
const int kExpectedRemovedSegments = kRepeat + 1;
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
XmlNodeEqual(ExpectedXml(
|
||||
expected_s_element, 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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
const uint64_t kSize = 20000;
|
||||
|
||||
const uint64_t kOneSecondDuration = kDefaultTimeScale;
|
||||
const uint64_t kOneSecondSegmentRepeat = 99;
|
||||
AddSegments(kInitialStartTime, kOneSecondDuration, kSize,
|
||||
kOneSecondSegmentRepeat);
|
||||
const uint64_t first_s_element_end_time =
|
||||
kInitialStartTime + kOneSecondDuration * (kOneSecondSegmentRepeat + 1);
|
||||
|
||||
const uint64_t kTwoSecondDuration = 2 * kDefaultTimeScale;
|
||||
const uint64_t kTwoSecondSegmentRepeat = 20;
|
||||
AddSegments(first_s_element_end_time, kTwoSecondDuration, kSize,
|
||||
kTwoSecondSegmentRepeat);
|
||||
|
||||
const uint64_t 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);
|
||||
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
XmlNodeEqual(ExpectedXml(
|
||||
expected_s_element, 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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 1;
|
||||
const uint64_t kDuration1 = static_cast<uint64_t>(kDefaultTimeScale * 1.5);
|
||||
const uint64_t kSize = 20000;
|
||||
const uint64_t kRepeat1 = 1;
|
||||
|
||||
AddSegments(kInitialStartTime, kDuration1, kSize, kRepeat1);
|
||||
|
||||
const uint64_t first_s_element_end_time =
|
||||
kInitialStartTime + kDuration1 * (kRepeat1 + 1);
|
||||
|
||||
const uint64_t kTwoSecondDuration = 2 * kDefaultTimeScale;
|
||||
const uint64_t 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);
|
||||
EXPECT_THAT(representation_->GetXml().get(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, 2)));
|
||||
}
|
||||
|
||||
// Gap between S elements but both should be included.
|
||||
TEST_F(TimeShiftBufferDepthTest, NormalGap) {
|
||||
const int kTimeShiftBufferDepth = 10;
|
||||
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
const uint64_t kDuration = kDefaultTimeScale;
|
||||
const uint64_t kSize = 20000;
|
||||
const uint64_t 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_t>(kTimeShiftBufferDepth));
|
||||
CHECK_EQ(kDuration, kDefaultTimeScale);
|
||||
|
||||
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
const uint64_t first_s_element_end_time =
|
||||
kInitialStartTime + kDuration * (kRepeat + 1);
|
||||
|
||||
const uint64_t 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);
|
||||
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
XmlNodeEqual(ExpectedXml(expected_s_element, 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()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
const uint64_t kDuration = kDefaultTimeScale;
|
||||
const uint64_t kSize = 20000;
|
||||
const uint64_t kRepeat = 6;
|
||||
AddSegments(kInitialStartTime, kDuration, kSize, kRepeat);
|
||||
|
||||
const uint64_t first_s_element_end_time =
|
||||
kInitialStartTime + kDuration * (kRepeat + 1);
|
||||
|
||||
// Big enough gap so first S element should not be there.
|
||||
const uint64_t gap_s_element_start_time =
|
||||
first_s_element_end_time +
|
||||
(kTimeShiftBufferDepth + 1) * kDefaultTimeScale;
|
||||
const uint64_t kSecondSElementRepeat = 9;
|
||||
static_assert(
|
||||
kSecondSElementRepeat < static_cast<uint64_t>(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;
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
XmlNodeEqual(ExpectedXml(
|
||||
expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
|
||||
}
|
||||
|
||||
// Check if startNumber is working correctly.
|
||||
TEST_F(TimeShiftBufferDepthTest, ManySegments) {
|
||||
const int kTimeShiftBufferDepth = 1;
|
||||
mutable_mpd_options()->mpd_params.time_shift_buffer_depth =
|
||||
kTimeShiftBufferDepth;
|
||||
|
||||
const uint64_t kInitialStartTime = 0;
|
||||
const uint64_t kDuration = kDefaultTimeScale;
|
||||
const uint64_t kSize = 20000;
|
||||
const uint64_t kRepeat = 10000;
|
||||
const uint64_t 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_t>(kExpectedSegmentsRepeat));
|
||||
|
||||
EXPECT_THAT(
|
||||
representation_->GetXml().get(),
|
||||
XmlNodeEqual(ExpectedXml(
|
||||
expected_s_element, kDefaultStartNumber + kExpectedRemovedSegments)));
|
||||
}
|
||||
|
||||
} // namespace shaka
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?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" publishTime="2016-01-11T15:10:24Z">
|
||||
<Period id="0" start="PT0S">
|
||||
<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" sar="1:1">
|
||||
<SegmentTemplate timescale="1000" initialization="init.mp4" media="$Time$.mp4">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="10"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
|
@ -1,23 +0,0 @@
|
|||
bandwidth: 195857
|
||||
audio_info {
|
||||
codec: "mp4a.40.2"
|
||||
sampling_frequency: 44100
|
||||
time_scale: 44100
|
||||
num_channels: 2
|
||||
decoder_config: "\022\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
}
|
||||
content_protections {
|
||||
scheme_id_uri: "http://foo.com/"
|
||||
}
|
||||
init_range {
|
||||
begin: 0
|
||||
end: 863
|
||||
}
|
||||
index_range {
|
||||
begin: 864
|
||||
end: 931
|
||||
}
|
||||
media_file_name: "encrypted_audio.mp4"
|
||||
media_duration_seconds: 24.009434
|
||||
reference_time_scale: 44100
|
||||
container_type: CONTAINER_MP4
|
|
@ -1,14 +0,0 @@
|
|||
<?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" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT24.00943374633789S">
|
||||
<Period>
|
||||
<AdaptationSet id="0" contentType="audio">
|
||||
<Representation id="0" bandwidth="195857" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>encrypted_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="864-931" timescale="44100">
|
||||
<Initialization range="0-863"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
|
@ -23,7 +23,6 @@ class MediaInfo;
|
|||
const char kFileNameVideoMediaInfo1[] = "video_media_info1.txt";
|
||||
const char kFileNameVideoMediaInfo2[] = "video_media_info2.txt";
|
||||
const char kFileNameAudioMediaInfo1[] = "audio_media_info1.txt";
|
||||
const char kFileNameEncytpedAudioMediaInfo[] = "encrypted_audio_media_info.txt";
|
||||
|
||||
// These are the expected output files.
|
||||
const char kFileNameExpectedMpdOutputVideo1[] =
|
||||
|
@ -38,11 +37,6 @@ const char kFileNameExpectedMpdOutputAudio1[] =
|
|||
const char kFileNameExpectedMpdOutputAudio1AndVideo1[] =
|
||||
"audio_media_info1_video_media_info1_expected_mpd_output.txt";
|
||||
|
||||
const char kFileNameExpectedMpdOutputEncryptedAudio[] =
|
||||
"encrypted_audio_media_info_expected_output.txt";
|
||||
|
||||
const char kFileNameExpectedMpdOutputDynamicNormal[] = "dynamic_normal_mpd.txt";
|
||||
|
||||
// Returns the path to test data with |file_name|. Use constants above to get
|
||||
// path to the test files.
|
||||
base::FilePath GetTestDataFilePath(const std::string& file_name);
|
||||
|
|
Loading…
Reference in New Issue