shaka-packager/packager/mpd/base/mpd_builder_unittest.cc

1532 lines
56 KiB
C++

// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include <gtest/gtest.h>
#include <inttypes.h>
#include <libxml/xmlstring.h>
#include "base/strings/string_piece.h"
#include "packager/base/file_util.h"
#include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h"
#include "packager/base/strings/string_util.h"
#include "packager/base/strings/stringprintf.h"
#include "packager/media/file/file.h"
#include "packager/mpd/base/mpd_builder.h"
#include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/test/mpd_builder_test_helper.h"
#include "packager/mpd/test/xml_compare.h"
namespace edash_packager {
using base::FilePath;
namespace {
// Any number for {AdaptationSet,Representation} ID. Required to create
// either objects. Not checked in test.
const uint32_t kAnyRepresentationId = 1;
const uint32_t kAnyAdaptationSetId = 1;
const char kSElementTemplate[] =
"<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\" r=\"%" PRIu64 "\"/>\n";
const char kSElementTemplateWithoutR[] =
"<S t=\"%" PRIu64 "\" d=\"%" PRIu64 "\"/>\n";
const int kDefaultStartNumber = 1;
// Get 'id' attribute from |node|, convert it to std::string and convert it to a
// number.
void ExpectXmlElementIdEqual(xmlNodePtr node, uint32_t id) {
const char kId[] = "id";
xml::ScopedXmlPtr<xmlChar>::type id_attribute_xml_str(
xmlGetProp(node, BAD_CAST kId));
ASSERT_TRUE(id_attribute_xml_str);
unsigned id_attribute_unsigned = 0;
std::string id_attribute_str =
reinterpret_cast<const char*>(id_attribute_xml_str.get());
ASSERT_TRUE(base::StringToUint(id_attribute_str, &id_attribute_unsigned));
ASSERT_EQ(id, id_attribute_unsigned);
}
// Using template to support both AdaptationSet and Representation.
template <typename T>
void CheckIdEqual(uint32_t expected_id, T* node) {
ASSERT_EQ(expected_id, node->id());
// Also check if the XML generated by libxml2 has the correct id attribute.
xml::ScopedXmlPtr<xmlNode>::type node_xml(node->GetXml());
ASSERT_NO_FATAL_FAILURE(ExpectXmlElementIdEqual(node_xml.get(), expected_id));
}
void ExpectAttributeEqString(base::StringPiece attribute,
base::StringPiece expected_value,
xmlNodePtr node) {
xml::ScopedXmlPtr<xmlChar>::type attribute_xml_str(
xmlGetProp(node, BAD_CAST attribute.data()));
ASSERT_TRUE(attribute_xml_str);
EXPECT_STREQ(expected_value.data(),
reinterpret_cast<const char*>(attribute_xml_str.get()));
}
// |attribute| should not be set in |node|.
void ExpectAttributeNotSet(base::StringPiece attribute, xmlNodePtr node) {
xml::ScopedXmlPtr<xmlChar>::type attribute_xml_str(
xmlGetProp(node, BAD_CAST attribute.data()));
ASSERT_FALSE(attribute_xml_str);
}
} // namespace
template <MpdBuilder::MpdType type>
class MpdBuilderTest: public ::testing::Test {
public:
MpdBuilderTest() : mpd_(type, MpdOptions()), representation_() {}
virtual ~MpdBuilderTest() {}
void CheckMpd(const std::string& expected_output_file) {
std::string mpd_doc;
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
ASSERT_TRUE(ValidateMpdSchema(mpd_doc));
ASSERT_NO_FATAL_FAILURE(
ExpectMpdToEqualExpectedOutputFile(mpd_doc, expected_output_file));
}
protected:
void AddRepresentation(const MediaInfo& media_info) {
AdaptationSet* adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(adaptation_set);
Representation* representation =
adaptation_set->AddRepresentation(media_info);
ASSERT_TRUE(representation);
representation_ = representation;
}
MpdBuilder mpd_;
// We usually need only one representation.
Representation* representation_; // Owned by |mpd_|.
private:
DISALLOW_COPY_AND_ASSIGN(MpdBuilderTest);
};
class StaticMpdBuilderTest : public MpdBuilderTest<MpdBuilder::kStatic> {};
// Use this test name for things that are common to both static an dynamic
// mpd builder tests.
typedef StaticMpdBuilderTest CommonMpdBuilderTest;
class DynamicMpdBuilderTest : public MpdBuilderTest<MpdBuilder::kDynamic> {
public:
virtual ~DynamicMpdBuilderTest() {}
// Anchors availabilityStartTime so that the test result doesn't depend on the
// current time.
virtual void SetUp() {
mpd_.availability_start_time_ = "2011-12-25T12:30:00";
}
MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; }
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 DynamicMpdBuilderTest {
public:
SegmentTemplateTest()
: bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks) {}
virtual ~SegmentTemplateTest() {}
virtual void SetUp() {
DynamicMpdBuilderTest::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) {
const char kOutputTemplate[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" "
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\" "
"availabilityStartTime=\"2011-12-25T12:30:00\" minBufferTime=\"PT2S\" "
"type=\"dynamic\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\">\n"
" <Period start=\"PT0S\">\n"
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
" frameRate=\"10/5\" contentType=\"video\""
" par=\"3:2\">\n"
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
"height=\"480\" frameRate=\"10/5\" 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() {}
virtual ~TimeShiftBufferDepthTest() {}
// This function is tricky. It does not call SegmentTemplateTest::Setup() so
// that it does not automatically add a representation, that has $Time$
// template.
virtual void SetUp() {
DynamicMpdBuilderTest::SetUp();
// The only diff with current GetDefaultMediaInfo() is that this uses
// $Number$ for segment template.
const char kMediaInfo[] =
"video_info {\n"
" codec: 'avc1.010101'\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" 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) {
const char kOutputTemplate[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" "
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\" "
"availabilityStartTime=\"2011-12-25T12:30:00\" minBufferTime=\"PT2S\" "
"type=\"dynamic\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"timeShiftBufferDepth=\"PT%dS\">\n"
" <Period start=\"PT0S\">\n"
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
" frameRate=\"10/2\" contentType=\"video\""
" par=\"3:2\">\n"
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
"height=\"480\" frameRate=\"10/2\" 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;
}
};
// Verify that AdaptationSet@group can be set and unset.
TEST_F(CommonMpdBuilderTest, SetAdaptationSetGroup) {
base::AtomicSequenceNumber sequence_counter;
AdaptationSet adaptation_set(
kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter);
adaptation_set.set_group(1);
xml::ScopedXmlPtr<xmlNode>::type xml_with_group(adaptation_set.GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("group", "1", xml_with_group.get()));
// Unset by passing a negative value.
adaptation_set.set_group(-1);
xml::ScopedXmlPtr<xmlNode>::type xml_without_group(adaptation_set.GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("group", xml_without_group.get()));
}
// Verify that Representation::Init() works with all "required" fields of
// MedieInfo proto.
TEST_F(CommonMpdBuilderTest, ValidMediaInfo) {
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";
Representation representation(
ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId);
EXPECT_TRUE(representation.Init());
}
// Verify that Representation::Init() fails if a required field is missing.
TEST_F(CommonMpdBuilderTest, InvalidMediaInfo) {
// Missing width.
const char kTestMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\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";
Representation representation(
ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId);
EXPECT_FALSE(representation.Init());
}
// Basic check that the fields in video info are in the XML.
TEST_F(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml) {
const char kTestMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
Representation representation(
ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId);
EXPECT_TRUE(representation.Init());
xml::ScopedXmlPtr<xmlNode>::type node_xml(representation.GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("codecs", "avc1", node_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("width", "1280", node_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("height", "720", node_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("sar", "1:1", node_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("frameRate", "10/10", node_xml.get()));
}
// Verify that content type is set correctly if video info is present in
// MediaInfo.
TEST_F(CommonMpdBuilderTest, CheckAdaptationSetVideoContentType) {
base::AtomicSequenceNumber sequence_counter;
const char kVideoMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
AdaptationSet adaptation_set(
kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter);
adaptation_set.AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo));
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set.GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("contentType", "video", node_xml.get()));
}
// Verify that content type is set correctly if audio info is present in
// MediaInfo.
TEST_F(CommonMpdBuilderTest, CheckAdaptationSetAudioContentType) {
base::AtomicSequenceNumber sequence_counter;
const char kAudioMediaInfo[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
"}\n"
"container_type: 1\n";
AdaptationSet adaptation_set(
kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter);
adaptation_set.AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo));
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set.GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("contentType", "audio", node_xml.get()));
}
// Verify that content type is set correctly if text info is present in
// MediaInfo.
// TODO(rkuroiwa): Enable this once text support is implemented.
// This fails because it fails to get the codec, therefore Representation
// creation fails.
TEST_F(CommonMpdBuilderTest, DISABLED_CheckAdaptationSetTextContentType) {
base::AtomicSequenceNumber sequence_counter;
const char kTextMediaInfo[] =
"text_info {\n"
" format: 'ttml'\n"
" language: 'en'\n"
"}\n"
"container_type: 1\n";
AdaptationSet adaptation_set(
kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter);
adaptation_set.AddRepresentation(ConvertToMediaInfo(kTextMediaInfo));
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set.GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("contentType", "text", node_xml.get()));
}
TEST_F(CommonMpdBuilderTest, CheckAdaptationSetId) {
base::AtomicSequenceNumber sequence_counter;
const uint32_t kAdaptationSetId = 42;
AdaptationSet adaptation_set(
kAdaptationSetId, "", MpdOptions(), &sequence_counter);
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kAdaptationSetId, &adaptation_set));
}
// Verify AdaptationSet::AddRole() works for "main" role.
TEST_F(CommonMpdBuilderTest, AdaptationAddRoleElementMain) {
MpdBuilder mpd_builder(MpdBuilder::kStatic, MpdOptions());
AdaptationSet* adaptation_set = mpd_builder.AddAdaptationSet("");
adaptation_set->AddRole(AdaptationSet::kRoleMain);
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(adaptation_set->GetXml());
// The empty contentType is sort of a side effect of being able to generate an
// MPD without adding any Representations.
const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\"\n"
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\"\n"
" minBufferTime=\"PT2S\" type=\"static\"\n"
" profiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\"\n"
" mediaPresentationDuration=\"PT0S\">\n"
" <Period>\n"
" <AdaptationSet id=\"0\" contentType=\"\">\n"
" <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>\n"
" </AdaptationSet>\n"
" </Period>\n"
"</MPD>";
std::string mpd_output;
EXPECT_TRUE(mpd_builder.ToString(&mpd_output));
EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output))
<< "Expected " << kExpectedOutput << std::endl << "Actual: " << mpd_output;
}
// Verify that if all video Representations in an AdaptationSet have the same
// frame rate, AdaptationSet also has a frameRate attribute.
TEST_F(CommonMpdBuilderTest, AdapatationSetFrameRate) {
const char kVideoMediaInfo1[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" frame_duration: 3\n"
"}\n"
"container_type: 1\n";
const char kVideoMediaInfo2[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" frame_duration: 3\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo1)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo2)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("frameRate", "10/3", adaptation_set_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("maxFrameRate", adaptation_set_xml.get()));
}
// Verify that if there are videos with different frame rates, the maxFrameRate
// is set.
TEST_F(CommonMpdBuilderTest, AdapatationSetMaxFrameRate) {
// 30fps video.
const char kVideoMediaInfo30fps[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 720\n"
" height: 480\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
"}\n"
"container_type: 1\n";
const char kVideoMediaInfo15fps[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 720\n"
" height: 480\n"
" time_scale: 3000\n"
" frame_duration: 200\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo30fps)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo15fps)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString("maxFrameRate", "3000/100",
adaptation_set_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("frameRate", adaptation_set_xml.get()));
}
// Verify that if the picture aspect ratio of all the Representations are the
// same, @par attribute is present.
TEST_F(CommonMpdBuilderTest, AdaptationSetParAllSame) {
const char k720pVideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
const char k1080pVideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1920\n"
" height: 1080\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
// Note that this has non-1 pixel width and height.
// Which makes the par 16:9.
const char k360pVideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 720\n"
" height: 360\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 8\n"
" pixel_height: 9\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k720pVideoInfo)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k1080pVideoInfo)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k360pVideoInfo)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString("par", "16:9",
adaptation_set_xml.get()));
}
// Verify that adding Representations with different par will generate
// AdaptationSet without @par.
TEST_F(CommonMpdBuilderTest, AdaptationSetParDifferent) {
const char k16by9VideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
// Note that 720:360 is 2:1 where as 720p (above) is 16:9.
const char k2by1VideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 720\n"
" height: 360\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k16by9VideoInfo)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k2by1VideoInfo)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("par", adaptation_set_xml.get()));
}
// Verify that adding Representation without pixel_width and pixel_height will
// not set @par.
TEST_F(CommonMpdBuilderTest, AdaptationSetParUnknown) {
const char kUknownPixelWidthAndHeight[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kUknownPixelWidthAndHeight)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("par", adaptation_set_xml.get()));
}
// Catch the case where it ends up wrong if integer division is used to check
// the frame rate.
// IOW, A/B != C/D but when using integer division A/B == C/D.
// SO, maxFrameRate should be set instead of frameRate.
TEST_F(CommonMpdBuilderTest,
AdapatationSetMaxFrameRateIntegerDivisionEdgeCase) {
// 11/3 != 10/3 but IntegerDiv(11,3) == IntegerDiv(10,3).
const char kVideoMediaInfo1[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 720\n"
" height: 480\n"
" time_scale: 11\n"
" frame_duration: 3\n"
"}\n"
"container_type: 1\n";
const char kVideoMediaInfo2[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" frame_duration: 3\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo1)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo2)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString("maxFrameRate", "11/3",
adaptation_set_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("frameRate", adaptation_set_xml.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) {
// Both 720p.
const char kVideoMediaInfo1[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
"}\n"
"container_type: 1\n";
const char kVideoMediaInfo2[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 3000\n"
" frame_duration: 200\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo1)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo2)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
ASSERT_NO_FATAL_FAILURE(
ExpectAttributeEqString("width", "1280", adaptation_set_xml.get()));
ASSERT_NO_FATAL_FAILURE(
ExpectAttributeEqString("height", "720", adaptation_set_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("maxWidth", adaptation_set_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("maxHeight", adaptation_set_xml.get()));
}
// Verify that the maxWidth and maxHeight attribute are set if there are
// multiple video resolutions.
TEST_F(StaticMpdBuilderTest, AdapatationSetMaxWidthAndMaxHeight) {
const char kVideoMediaInfo1080p[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 1920\n"
" height: 1080\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
"}\n"
"container_type: 1\n";
const char kVideoMediaInfo720p[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo1080p)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo720p)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("maxWidth", "1920", adaptation_set_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeEqString("maxHeight", "1080", adaptation_set_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("width", adaptation_set_xml.get()));
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("height", adaptation_set_xml.get()));
}
TEST_F(CommonMpdBuilderTest, CheckRepresentationId) {
const MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
const uint32_t kRepresentationId = 1;
Representation representation(
video_media_info, MpdOptions(), kRepresentationId);
EXPECT_TRUE(representation.Init());
ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kRepresentationId, &representation));
}
// TODO(rkuroiwa): Better way to test this is to use GetXml(). Check that
// frameRate is set once the patch that adds frameRate attribute lands.
// For now, check that media_info_ owned by Representation has
// frame_duration = sample_duration.
TEST_F(CommonMpdBuilderTest, SetSampleDuration) {
const MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
const uint32_t kRepresentationId = 1;
Representation representation(
video_media_info, MpdOptions(), kRepresentationId);
EXPECT_TRUE(representation.Init());
representation.SetSampleDuration(2u);
EXPECT_EQ(2u,
representation.media_info_.video_info().frame_duration());
}
// Verify that AdaptationSet::AddContentProtection() works.
TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) {
const char kVideoMediaInfo1080p[] =
"video_info {\n"
" codec: \"avc1\"\n"
" width: 1920\n"
" height: 1080\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
"}\n"
"container_type: 1\n";
ContentProtectionElement content_protection;
content_protection.scheme_id_uri = "someuri";
content_protection.value = "some value";
Element pssh;
pssh.name = "cenc:pssh";
pssh.content = "any value";
content_protection.subelements.push_back(pssh);
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(kVideoMediaInfo1080p)));
video_adaptation_set->AddContentProtectionElement(content_protection);
const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\""
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
" 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=\"PT0S\">"
" <Period>"
" <AdaptationSet id=\"0\" contentType=\"video\" width=\"1920\""
" height=\"1080\" frameRate=\"3000/100\">"
" <ContentProtection schemeIdUri=\"someuri\" value=\"some value\">"
" <cenc:pssh>any value</cenc:pssh>"
" </ContentProtection>"
" <Representation id=\"0\" bandwidth=\"0\" codecs=\"avc1\""
" mimeType=\"video/mp4\" width=\"1920\" height=\"1080\""
" frameRate=\"3000/100\"/>"
" </AdaptationSet>"
" </Period>"
"</MPD>";
std::string mpd_output;
ASSERT_TRUE(mpd_.ToString(&mpd_output));
EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output));
}
// Add one video check the output.
TEST_F(StaticMpdBuilderTest, Video) {
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
ASSERT_NO_FATAL_FAILURE(AddRepresentation(video_media_info));
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputVideo1));
}
// Add both video and audio and check the output.
TEST_F(StaticMpdBuilderTest, VideoAndAudio) {
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
MediaInfo audio_media_info = GetTestMediaInfo(kFileNameAudioMediaInfo1);
// The order matters here to check against expected output.
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(audio_adaptation_set);
Representation* audio_representation =
audio_adaptation_set->AddRepresentation(audio_media_info);
ASSERT_TRUE(audio_representation);
Representation* video_representation =
video_adaptation_set->AddRepresentation(video_media_info);
ASSERT_TRUE(video_representation);
EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputAudio1AndVideo1));
}
// MPD schema has strict ordering. AudioChannelConfiguration must appear before
// ContentProtection.
// Also test that Representation::AddContentProtection() works.
TEST_F(StaticMpdBuilderTest, AudioChannelConfigurationWithContentProtection) {
const char kTestMediaInfo[] =
"bandwidth: 195857\n"
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 44100\n"
" num_channels: 2\n"
"}\n"
"init_range {\n"
" begin: 0\n"
" end: 863\n"
"}\n"
"index_range {\n"
" begin: 864\n"
" end: 931\n"
"}\n"
"media_file_name: 'encrypted_audio.mp4'\n"
"media_duration_seconds: 24.009434\n"
"reference_time_scale: 44100\n"
"container_type: CONTAINER_MP4\n";
const char kExpectedOutput[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\""
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
" 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\"/>"
" <ContentProtection schemeIdUri=\"http://foo.com/\">"
" <cenc:pssh>anything</cenc:pssh>"
" </ContentProtection>"
" <BaseURL>encrypted_audio.mp4</BaseURL>"
" <SegmentBase indexRange=\"864-931\" timescale=\"44100\">"
" <Initialization range=\"0-863\"/>"
" </SegmentBase>"
" </Representation>"
" </AdaptationSet>"
" </Period>"
"</MPD>";
ContentProtectionElement content_protection;
content_protection.scheme_id_uri = "http://foo.com/";
Element pssh;
pssh.name = "cenc:pssh";
pssh.content = "anything";
content_protection.subelements.push_back(pssh);
MediaInfo audio_media_info = ConvertToMediaInfo(kTestMediaInfo);
AdaptationSet* audio_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(audio_adaptation_set);
Representation* audio_representation =
audio_adaptation_set->AddRepresentation(audio_media_info);
ASSERT_TRUE(audio_representation);
audio_representation->AddContentProtectionElement(content_protection);
std::string mpd_output;
ASSERT_TRUE(mpd_.ToString(&mpd_output));
EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output));
}
// Static profile requires bandwidth to be set because it has no other way to
// get the bandwidth for the Representation.
TEST_F(StaticMpdBuilderTest, MediaInfoMissingBandwidth) {
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
video_media_info.clear_bandwidth();
AddRepresentation(video_media_info);
std::string mpd_doc;
ASSERT_FALSE(mpd_.ToString(&mpd_doc));
}
TEST_F(StaticMpdBuilderTest, WriteToFile) {
MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1);
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
Representation* video_representation =
video_adaptation_set->AddRepresentation(video_media_info);
ASSERT_TRUE(video_representation);
base::FilePath file_path;
ASSERT_TRUE(base::CreateTemporaryFile(&file_path));
media::File* file = media::File::Open(file_path.value().data(), "w");
ASSERT_TRUE(file);
ASSERT_TRUE(mpd_.WriteMpdToFile(file));
ASSERT_TRUE(file->Close());
std::string file_content;
ASSERT_TRUE(base::ReadFileToString(file_path, &file_content));
ASSERT_NO_FATAL_FAILURE(ExpectMpdToEqualExpectedOutputFile(
file_content, kFileNameExpectedMpdOutputVideo1));
const bool kNonRecursive = false;
EXPECT_TRUE(DeleteFile(file_path, kNonRecursive));
}
// Check whether the attributes are set correctly for dynamic <MPD> element.
// This test must use ASSERT_EQ for comparison because XmlEqual() cannot
// handle namespaces correctly yet.
TEST_F(DynamicMpdBuilderTest, CheckMpdAttributes) {
static const char kExpectedOutput[] =
"<?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\" "
"xmlns:cenc=\"urn:mpeg:cenc:2013\" "
"minBufferTime=\"PT2S\" "
"type=\"dynamic\" "
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"availabilityStartTime=\"2011-12-25T12:30:00\">\n"
" <Period start=\"PT0S\"/>\n"
"</MPD>\n";
std::string mpd_doc;
ASSERT_TRUE(mpd_.ToString(&mpd_doc));
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()->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()->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()->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()->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()->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()->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()->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;
COMPILE_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()->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));
}
TEST(RelativePaths, PathsModified) {
const std::string kCommonPath(FilePath("foo").Append("bar").value());
const std::string kMediaFileBase("media.mp4");
const std::string kInitSegmentBase("init.mp4");
const std::string kSegmentTemplateBase("segment-$Number$.mp4");
const std::string kMediaFile(
FilePath(kCommonPath).Append(kMediaFileBase).value());
const std::string kInitSegment(
FilePath(kCommonPath).Append(kInitSegmentBase).value());
const std::string kSegmentTemplate(
FilePath(kCommonPath).Append(kSegmentTemplateBase).value());
const std::string kMpd(FilePath(kCommonPath).Append("media.mpd").value());
MediaInfo media_info;
media_info.set_media_file_name(kMediaFile);
media_info.set_init_segment_name(kInitSegment);
media_info.set_segment_template(kSegmentTemplate);
MpdBuilder::MakePathsRelativeToMpd(kMpd, &media_info);
EXPECT_EQ(kMediaFileBase, media_info.media_file_name());
EXPECT_EQ(kInitSegmentBase, media_info.init_segment_name());
EXPECT_EQ(kSegmentTemplateBase, media_info.segment_template());
}
TEST(RelativePaths, PathsNotModified) {
const std::string kMediaCommon(FilePath("foo").Append("bar").value());
const std::string kMediaFileBase("media.mp4");
const std::string kInitSegmentBase("init.mp4");
const std::string kSegmentTemplateBase("segment-$Number$.mp4");
const std::string kMediaFile(
FilePath(kMediaCommon).Append(kMediaFileBase).value());
const std::string kInitSegment(
FilePath(kMediaCommon).Append(kInitSegmentBase).value());
const std::string kSegmentTemplate(
FilePath(kMediaCommon).Append(kSegmentTemplateBase).value());
const std::string kMpd(
FilePath("foo").Append("baz").Append("media.mpd").value());
MediaInfo media_info;
media_info.set_media_file_name(kMediaFile);
media_info.set_init_segment_name(kInitSegment);
media_info.set_segment_template(kSegmentTemplate);
MpdBuilder::MakePathsRelativeToMpd(kMpd, &media_info);
EXPECT_EQ(kMediaFile, media_info.media_file_name());
EXPECT_EQ(kInitSegment, media_info.init_segment_name());
EXPECT_EQ(kSegmentTemplate, media_info.segment_template());
}
} // namespace edash_packager