From cacdab22093f148435f0f1f7da87e57fe02615dd Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Thu, 14 Dec 2017 13:30:16 -0800 Subject: [PATCH] Split mpd_unittest.cc into three files Move AdaptationSet related tests to adaptation_set_unittest.cc and Representation related tests to representation_unittest.cc. Change-Id: I360e9aea3ba08a32e5ba3b0e8b34345f2b3f0b20 --- packager/mpd/base/adaptation_set.h | 3 +- packager/mpd/base/adaptation_set_unittest.cc | 1028 ++++++++++++++ packager/mpd/base/mpd_builder_unittest.cc | 1330 ------------------ packager/mpd/base/representation.h | 3 +- packager/mpd/base/representation_unittest.cc | 369 +++++ packager/mpd/mpd.gyp | 2 + 6 files changed, 1401 insertions(+), 1334 deletions(-) create mode 100644 packager/mpd/base/adaptation_set_unittest.cc create mode 100644 packager/mpd/base/representation_unittest.cc diff --git a/packager/mpd/base/adaptation_set.h b/packager/mpd/base/adaptation_set.h index 4c947f3475..74cc5db8cc 100644 --- a/packager/mpd/base/adaptation_set.h +++ b/packager/mpd/base/adaptation_set.h @@ -167,8 +167,7 @@ class AdaptationSet { AdaptationSet& operator=(const AdaptationSet&) = delete; friend class MpdBuilder; - template - friend class MpdBuilderTest; + friend class AdaptationSetTest; // kSegmentAlignmentUnknown means that it is uncertain if the // (sub)segments are aligned or not. diff --git a/packager/mpd/base/adaptation_set_unittest.cc b/packager/mpd/base/adaptation_set_unittest.cc new file mode 100644 index 0000000000..f02b83a350 --- /dev/null +++ b/packager/mpd/base/adaptation_set_unittest.cc @@ -0,0 +1,1028 @@ +// Copyright 2017 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 "packager/mpd/base/adaptation_set.h" + +#include +#include + +#include "packager/mpd/base/content_protection_element.h" +#include "packager/mpd/base/representation.h" +#include "packager/mpd/test/mpd_builder_test_helper.h" +#include "packager/mpd/test/xml_compare.h" + +using ::testing::Not; + +namespace shaka { + +namespace { +const uint32_t kAnyAdaptationSetId = 1; +const char kNoLanguage[] = ""; +} // namespace + +class AdaptationSetTest : public ::testing::Test { + public: + std::unique_ptr CreateAdaptationSet(uint32_t adaptation_set_id, + const std::string& lang) { + return std::unique_ptr(new AdaptationSet( + adaptation_set_id, lang, mpd_options_, &representation_counter_)); + } + + protected: + MpdOptions mpd_options_; + base::AtomicSequenceNumber representation_counter_; +}; + +class OnDemandAdaptationSetTest : public AdaptationSetTest { + public: + void SetUp() override { mpd_options_.dash_profile = DashProfile::kOnDemand; } +}; + +class LiveAdaptationSetTest : public AdaptationSetTest { + public: + void SetUp() override { mpd_options_.dash_profile = DashProfile::kLive; } +}; + +TEST_F(AdaptationSetTest, AddAdaptationSetSwitching) { + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + adaptation_set->AddAdaptationSetSwitching(1); + adaptation_set->AddAdaptationSetSwitching(2); + adaptation_set->AddAdaptationSetSwitching(8); + + // The empty contentType is sort of a side effect of being able to generate an + // MPD without adding any Representations. + const char kExpectedOutput[] = + "" + " " + ""; + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); +} + +// Verify that content type is set correctly if video info is present in +// MediaInfo. +TEST_F(AdaptationSetTest, CheckAdaptationSetVideoContentType) { + 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: CONTAINER_MP4\n"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo)); + EXPECT_THAT(adaptation_set->GetXml().get(), + AttributeEqual("contentType", "video")); +} + +// Verify that content type is set correctly if audio info is present in +// MediaInfo. +TEST_F(AdaptationSetTest, CheckAdaptationSetAudioContentType) { + 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: CONTAINER_MP4\n"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo)); + EXPECT_THAT(adaptation_set->GetXml().get(), + AttributeEqual("contentType", "audio")); +} + +// Verify that content type is set correctly if text info is present in +// MediaInfo. +TEST_F(AdaptationSetTest, CheckAdaptationSetTextContentType) { + const char kTextMediaInfo[] = + "text_info {\n" + " format: 'ttml'\n" + " language: 'en'\n" + "}\n" + "container_type: CONTAINER_TEXT\n"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "en"); + adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo)); + EXPECT_THAT(adaptation_set->GetXml().get(), + AttributeEqual("contentType", "text")); +} + +// Verify that language passed to the constructor sets the @lang field is set. +TEST_F(AdaptationSetTest, CheckLanguageAttributeSet) { + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "en"); + EXPECT_THAT(adaptation_set->GetXml().get(), AttributeEqual("lang", "en")); +} + +// Verify that language tags with subtags can still be converted. +TEST_F(AdaptationSetTest, CheckConvertLanguageWithSubtag) { + // "por-BR" is the long tag for Brazillian Portuguese. The short tag + // is "pt-BR", which is what should appear in the manifest. + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "por-BR"); + EXPECT_THAT(adaptation_set->GetXml().get(), AttributeEqual("lang", "pt-BR")); +} + +TEST_F(AdaptationSetTest, CheckAdaptationSetId) { + const uint32_t kAdaptationSetId = 42; + auto adaptation_set = CreateAdaptationSet(kAdaptationSetId, kNoLanguage); + EXPECT_THAT(adaptation_set->GetXml().get(), + AttributeEqual("id", std::to_string(kAdaptationSetId))); +} + +// Verify AdaptationSet::AddRole() works for "main" role. +TEST_F(AdaptationSetTest, AdaptationAddRoleElementMain) { + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + adaptation_set->AddRole(AdaptationSet::kRoleMain); + + // The empty contentType is sort of a side effect of being able to generate an + // MPD without adding any Representations. + const char kExpectedOutput[] = + "\n" + " \n" + ""; + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); +} + +// Add Role, ContentProtection, and Representation elements. Verify that +// ContentProtection -> Role -> Representation are in order. +TEST_F(AdaptationSetTest, CheckContentProtectionRoleRepresentationOrder) { + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + adaptation_set->AddRole(AdaptationSet::kRoleMain); + ContentProtectionElement any_content_protection; + any_content_protection.scheme_id_uri = "any_scheme"; + adaptation_set->AddContentProtectionElement(any_content_protection); + 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"; + adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo)); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + const char kExpectedOutput[] = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); +} + +// Verify that if all video Representations in an AdaptationSet have the same +// frame rate, AdaptationSet also has a frameRate attribute. +TEST_F(AdaptationSetTest, 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"; + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo1))); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("frameRate", "10/3")); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxFrameRate"))); +} + +// Verify that if there are videos with different frame rates, the maxFrameRate +// is set. +TEST_F(AdaptationSetTest, 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"; + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE(adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo30fps))); + ASSERT_TRUE(adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo15fps))); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), + AttributeEqual("maxFrameRate", "3000/100")); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate"))); +} + +// Verify that (max)FrameRate can be set by calling +// Representation::SetSampleDuration(). +TEST_F(AdaptationSetTest, + SetAdaptationFrameRateUsingRepresentationSetSampleDuration) { + // Note that frame duration is not set in the MediaInfos. It could be there + // and should not affect the behavior of the program. + // But to make it closer to a real live-profile use case, + // the frame duration is not set in the MediaInfo, instead it is set using + // SetSampleDuration(). + const char k480pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " pixel_width: 8\n" + " pixel_height: 9\n" + "}\n" + "container_type: 1\n"; + const char k360pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 640\n" + " height: 360\n" + " time_scale: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + Representation* representation_480p = + adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + Representation* representation_360p = + adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); + + // First, make sure that maxFrameRate nor frameRate are set because + // frame durations were not provided in the MediaInfo. + xml::scoped_xml_ptr no_frame_rate(adaptation_set->GetXml()); + EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("maxFrameRate"))); + EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("frameRate"))); + + // Then set same frame duration for the representations. (Given that the + // time scales match). + const uint32_t kSameFrameDuration = 3u; + representation_480p->SetSampleDuration(kSameFrameDuration); + representation_360p->SetSampleDuration(kSameFrameDuration); + + xml::scoped_xml_ptr same_frame_rate(adaptation_set->GetXml()); + EXPECT_THAT(same_frame_rate.get(), Not(AttributeSet("maxFrameRate"))); + EXPECT_THAT(same_frame_rate.get(), AttributeEqual("frameRate", "10/3")); + + // Then set 480p to be 5fps (10/2) so that maxFrameRate is set. + const uint32_t k5FPSFrameDuration = 2; + static_assert(k5FPSFrameDuration < kSameFrameDuration, + "frame_duration_must_be_shorter_for_max_frame_rate"); + representation_480p->SetSampleDuration(k5FPSFrameDuration); + + xml::scoped_xml_ptr max_frame_rate(adaptation_set->GetXml()); + EXPECT_THAT(max_frame_rate.get(), AttributeEqual("maxFrameRate", "10/2")); + EXPECT_THAT(max_frame_rate.get(), Not(AttributeSet("frameRate"))); +} + +// Verify that if the picture aspect ratio of all the Representations are the +// same, @par attribute is present. +TEST_F(AdaptationSetTest, AdaptationSetParAllSame) { + const char k480pVideoInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 854\n" + " height: 480\n" + " time_scale: 3000\n" + " frame_duration: 100\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + 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"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pVideoInfo))); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(k720pVideoInfo))); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(k1080pVideoInfo))); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pVideoInfo))); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("par", "16:9")); +} + +// Verify that adding Representations with different par will generate +// AdaptationSet without @par. +TEST_F(AdaptationSetTest, 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"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(k16by9VideoInfo))); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(k2by1VideoInfo))); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("par"))); +} + +// Verify that adding Representation without pixel_width and pixel_height will +// not set @par. +TEST_F(AdaptationSetTest, 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"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE(adaptation_set->AddRepresentation( + ConvertToMediaInfo(kUknownPixelWidthAndHeight))); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("par"))); +} + +// 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(AdaptationSetTest, 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"; + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo1))); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxFrameRate", "11/3")); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate"))); +} + +// Attribute values that are common to all the children Representations should +// propagate up to AdaptationSet. Otherwise, each Representation should have +// its own values. +TEST_F(AdaptationSetTest, BubbleUpAttributesToAdaptationSet) { + const char k1080p[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 1920\n" + " height: 1080\n" + " time_scale: 30\n" + " frame_duration: 1\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + // Different width from the one above. + const char kDifferentWidth[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 1080\n" + " height: 1080\n" + " time_scale: 30\n" + " frame_duration: 1\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + // Different height from ones above + const char kDifferentHeight[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 1440\n" + " height: 900\n" + " time_scale: 30\n" + " frame_duration: 1\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + const char kDifferentFrameRate[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 1920\n" + " height: 1080\n" + " time_scale: 15\n" + " frame_duration: 1\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE(adaptation_set->AddRepresentation(ConvertToMediaInfo(k1080p))); + + xml::scoped_xml_ptr all_attributes_on_adaptation_set( + adaptation_set->GetXml()); + EXPECT_THAT(all_attributes_on_adaptation_set.get(), + AttributeEqual("width", "1920")); + EXPECT_THAT(all_attributes_on_adaptation_set.get(), + AttributeEqual("height", "1080")); + EXPECT_THAT(all_attributes_on_adaptation_set.get(), + AttributeEqual("frameRate", "30/1")); + + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentWidth))); + xml::scoped_xml_ptr width_not_set(adaptation_set->GetXml()); + EXPECT_THAT(width_not_set.get(), Not(AttributeSet("width"))); + EXPECT_THAT(width_not_set.get(), AttributeEqual("height", "1080")); + EXPECT_THAT(width_not_set.get(), AttributeEqual("frameRate", "30/1")); + + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentHeight))); + xml::scoped_xml_ptr width_height_not_set(adaptation_set->GetXml()); + EXPECT_THAT(width_height_not_set.get(), Not(AttributeSet("width"))); + EXPECT_THAT(width_height_not_set.get(), Not(AttributeSet("height"))); + EXPECT_THAT(width_height_not_set.get(), AttributeEqual("frameRate", "30/1")); + + ASSERT_TRUE(adaptation_set->AddRepresentation( + ConvertToMediaInfo(kDifferentFrameRate))); + xml::scoped_xml_ptr no_common_attributes(adaptation_set->GetXml()); + EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("width"))); + EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("height"))); + EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("frameRate"))); +} + +// Verify that subsegmentAlignment is set to true if all the Representations' +// segments are aligned and the DASH profile is OnDemand. +// Also checking that not all Representations have to be added before calling +// AddNewSegment() on a Representation. +TEST_F(OnDemandAdaptationSetTest, SubsegmentAlignment) { + const char k480pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 8\n" + " pixel_height: 9\n" + "}\n" + "container_type: 1\n"; + const char k360pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 640\n" + " height: 360\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + // First use same start time and duration, and verify that subsegmentAlignment + // is set to true. + const uint64_t kStartTime = 0u; + const uint64_t kDuration = 10u; + const uint64_t kAnySize = 19834u; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + Representation* representation_480p = + adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + // Add a subsegment immediately before adding the 360p Representation. + // This should still work for VOD. + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); + + Representation* representation_360p = + adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); + representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + + xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); + EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true")); + + // Unknown because 480p has an extra subsegments. + representation_480p->AddNewSegment(11, 20, kAnySize); + xml::scoped_xml_ptr alignment_unknown(adaptation_set->GetXml()); + EXPECT_THAT(alignment_unknown.get(), + Not(AttributeSet("subsegmentAlignment"))); + + // Add segments that make them not aligned. + representation_360p->AddNewSegment(10, 1, kAnySize); + representation_360p->AddNewSegment(11, 19, kAnySize); + + xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); + EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment"))); +} + +// Verify that subsegmentAlignment can be force set to true. +TEST_F(OnDemandAdaptationSetTest, ForceSetsubsegmentAlignment) { + const char k480pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 8\n" + " pixel_height: 9\n" + "}\n" + "container_type: 1\n"; + const char k360pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 640\n" + " height: 360\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + Representation* representation_480p = + adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + Representation* representation_360p = + adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); + + // Use different starting times to make the segments "not aligned". + const uint64_t kStartTime1 = 1u; + const uint64_t kStartTime2 = 2u; + static_assert(kStartTime1 != kStartTime2, "StartTimesShouldBeDifferent"); + const uint64_t kDuration = 10u; + const uint64_t kAnySize = 19834u; + representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize); + representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize); + xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); + EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment"))); + + // Then force set the segment alignment attribute to true. + adaptation_set->ForceSetSegmentAlignment(true); + xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); + EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true")); +} + +// Verify that segmentAlignment is set to true if all the Representations +// segments' are aligned and the DASH profile is Live. +TEST_F(LiveAdaptationSetTest, SegmentAlignment) { + const char k480pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 8\n" + " pixel_height: 9\n" + "}\n" + "container_type: 1\n"; + const char k360pMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 640\n" + " height: 360\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + Representation* representation_480p = + adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + Representation* representation_360p = + adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); + + // First use same start time and duration, and verify that that + // segmentAlignment is set. + const uint64_t kStartTime = 0u; + const uint64_t kDuration = 10u; + const uint64_t kAnySize = 19834u; + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); + EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true")); + + // Add segments that make them not aligned. + representation_480p->AddNewSegment(11, 20, kAnySize); + representation_360p->AddNewSegment(10, 1, kAnySize); + representation_360p->AddNewSegment(11, 19, kAnySize); + + xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); + EXPECT_THAT(unaligned.get(), Not(AttributeSet("segmentAlignment"))); +} + +// Verify that the width and height attribute are set if all the video +// representations have the same width and height. +TEST_F(OnDemandAdaptationSetTest, 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"; + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo1))); + ASSERT_TRUE( + adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("width", "1280")); + EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("height", "720")); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxWidth"))); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxHeight"))); +} + +// Verify that the maxWidth and maxHeight attribute are set if there are +// multiple video resolutions. +TEST_F(OnDemandAdaptationSetTest, AdaptationSetMaxWidthAndMaxHeight) { + 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"; + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE(adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo1080p))); + ASSERT_TRUE(adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo720p))); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxWidth", "1920")); + EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxHeight", "1080")); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("width"))); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("height"))); +} + +// Verify that Representation::SetSampleDuration() works by checking that +// AdaptationSet@frameRate is in the XML. +TEST_F(AdaptationSetTest, SetSampleDuration) { + // Omit frame_duration so that SetSampleDuration() will set a new frameRate. + const char kVideoMediaInfo[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 1920\n" + " height: 1080\n" + " time_scale: 3000\n" + "}\n" + "container_type: 1\n"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + + const MediaInfo video_media_info = ConvertToMediaInfo(kVideoMediaInfo); + Representation* representation = + adaptation_set->AddRepresentation(video_media_info); + EXPECT_TRUE(representation->Init()); + + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate"))); + + representation->SetSampleDuration(2u); + adaptation_set_xml = adaptation_set->GetXml(); + EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("frameRate", "3000/2")); +} + +// Verify that AdaptationSet::AddContentProtection() and +// UpdateContentProtectionPssh() works. +TEST_F(AdaptationSetTest, AdaptationSetAddContentProtectionAndUpdate) { + 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 = + "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; + content_protection.value = "some value"; + Element pssh; + pssh.name = "cenc:pssh"; + pssh.content = "any value"; + content_protection.subelements.push_back(pssh); + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE(adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo1080p))); + adaptation_set->AddContentProtectionElement(content_protection); + + const char kExpectedOutput1[] = + "" + " " + " any value" + " " + " " + ""; + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput1)); + + adaptation_set->UpdateContentProtectionPssh( + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "new pssh value"); + const char kExpectedOutput2[] = + "" + " " + // TODO(rkuroiwa): Commenting this out for now because we want to remove + // the PSSH from the MPD. Uncomment this when the player supports updating + // pssh. + //" new pssh value" + " " + " " + ""; + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput2)); +} + +// Verify that if the ContentProtection element for the DRM without +// element is updated via UpdateContentProtectionPssh(), the element gets added. +// TODO(rkuroiwa): Until the player supports PSSH update, we remove the pssh +// element. Rename this test once it is supported. +TEST_F(AdaptationSetTest, UpdateToRemovePsshElement) { + 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 = + "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; + content_protection.value = "some value"; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + ASSERT_TRUE(adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo1080p))); + adaptation_set->AddContentProtectionElement(content_protection); + + const char kExpectedOutput1[] = + "" + " " + " " + " " + ""; + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput1)); + + adaptation_set->UpdateContentProtectionPssh( + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "added pssh value"); + const char kExpectedOutput2[] = + "" + " " + // TODO(rkuroiwa): Commenting this out for now because we want to remove + // teh PSSH from the MPD. Uncomment this when the player supports updating + // pssh. + //" added pssh value" + " " + " " + ""; + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput2)); +} + +// MPD schema has strict ordering. AudioChannelConfiguration must appear before +// ContentProtection. +// Also test that Representation::AddContentProtection() works. +TEST_F(OnDemandAdaptationSetTest, + 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[] = + "" + " " + " " + " " + " anything" + " " + " encrypted_audio.mp4" + " " + " " + " " + " " + ""; + + 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); + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + Representation* audio_representation = + adaptation_set->AddRepresentation(ConvertToMediaInfo(kTestMediaInfo)); + ASSERT_TRUE(audio_representation); + audio_representation->AddContentProtectionElement(content_protection); + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); +} + +// Verify that a text path works. +TEST_F(OnDemandAdaptationSetTest, Text) { + const char kTextMediaInfo[] = + "text_info {\n" + " format: 'ttml'\n" + " language: 'en'\n" + " type: SUBTITLE\n" + "}\n" + "media_duration_seconds: 35\n" + "bandwidth: 1000\n" + "media_file_name: 'subtitle.xml'\n" + "container_type: CONTAINER_TEXT\n"; + + const char kExpectedOutput[] = + "" + " \n" + " " + " subtitle.xml" + " " + ""; + + auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "en"); + Representation* text_representation = + adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo)); + ASSERT_TRUE(text_representation); + + EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); +} + +} // namespace shaka diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 382fdd276f..a66f9a2422 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -32,9 +32,6 @@ using ::testing::Not; 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 kNoLanguage[] = ""; const char kSElementTemplate[] = "\n"; const char kSElementTemplateWithoutR[] = @@ -51,25 +48,12 @@ class TestClock : public base::Clock { base::Time time_; }; -class MockRepresentationStateChangeListener - : public RepresentationStateChangeListener { - public: - MockRepresentationStateChangeListener() {} - ~MockRepresentationStateChangeListener() {} - - MOCK_METHOD2(OnNewSegmentForRepresentation, - void(uint64_t start_time, uint64_t duration)); - - MOCK_METHOD2(OnSetFrameRateForRepresentation, - void(uint32_t frame_duration, uint32_t timescale)); -}; } // namespace template class MpdBuilderTest : public ::testing::Test { public: MpdBuilderTest() : mpd_(MpdOptions()), representation_() { - mpd_options_.dash_profile = profile; mpd_.mpd_options_.dash_profile = profile; } ~MpdBuilderTest() override {} @@ -99,38 +83,12 @@ class MpdBuilderTest : public ::testing::Test { representation_ = representation; } - // TODO(rkuroiwa): Once std::forward() is allowed by chromium style guide, use - // variadic template and std::forward() so that we don't need to copy the - // constructor signatures. - std::unique_ptr CreateRepresentation( - const MediaInfo& media_info, - uint32_t representation_id, - std::unique_ptr - state_change_listener) { - return std::unique_ptr( - new Representation(media_info, mpd_options_, representation_id, - std::move(state_change_listener))); - } - - std::unique_ptr CreateAdaptationSet(uint32_t adaptation_set_id, - const std::string& lang) { - return std::unique_ptr(new AdaptationSet( - adaptation_set_id, lang, mpd_options_, &representation_counter_)); - } - - // Helper function to return an empty listener for tests that don't need - // it. - std::unique_ptr NoListener() { - return std::unique_ptr(); - } - MpdBuilder mpd_; // We usually need only one representation. Representation* representation_; // Owned by |mpd_|. private: - MpdOptions mpd_options_; base::AtomicSequenceNumber representation_counter_; DISALLOW_COPY_AND_ASSIGN(MpdBuilderTest); @@ -368,1201 +326,6 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest { } }; -TEST_F(CommonMpdBuilderTest, AddAdaptationSetSwitching) { - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - adaptation_set->AddAdaptationSetSwitching(1); - adaptation_set->AddAdaptationSetSwitching(2); - adaptation_set->AddAdaptationSetSwitching(8); - - // The empty contentType is sort of a side effect of being able to generate an - // MPD without adding any Representations. - const char kExpectedOutput[] = - "" - " " - ""; - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); -} - -// 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"; - - auto representation = CreateRepresentation( - ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); - EXPECT_TRUE(representation->Init()); -} - -// Verify that if VideoInfo, AudioInfo, or TextInfo is not set, Init() fails. -TEST_F(CommonMpdBuilderTest, VideoAudioTextInfoNotSet) { - const char kTestMediaInfo[] = "container_type: 1"; - - auto representation = CreateRepresentation( - ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); - EXPECT_FALSE(representation->Init()); -} - -// Verify that if more than one of VideoInfo, AudioInfo, or TextInfo is set, -// then Init() fails. -TEST_F(CommonMpdBuilderTest, VideoAndAudioInfoSet) { - 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" - "audio_info {\n" - " codec: 'mp4a.40.2'\n" - " sampling_frequency: 44100\n" - " time_scale: 1200\n" - " num_channels: 2\n" - "}\n" - "container_type: CONTAINER_MP4\n"; - - auto representation = CreateRepresentation( - ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); - EXPECT_FALSE(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"; - auto representation = CreateRepresentation( - ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); - 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"; - auto representation = CreateRepresentation( - ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); - ASSERT_TRUE(representation->Init()); - const char kExpectedOutput[] = - ""; - EXPECT_THAT(representation->GetXml().get(), XmlNodeEqual(kExpectedOutput)); -} - -TEST_F(CommonMpdBuilderTest, CheckVideoInfoVp8CodecInMp4) { - const char kTestMediaInfoCodecVp8[] = - "video_info {\n" - " codec: 'vp08.00.00.08.01.01.00.00'\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"; - auto representation = - CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8), - kAnyRepresentationId, NoListener()); - ASSERT_TRUE(representation->Init()); - EXPECT_THAT(representation->GetXml().get(), - AttributeEqual("codecs", "vp08.00.00.08.01.01.00.00")); -} - -// Check that vp8 codec string will be updated for backward compatibility -// support in webm. -TEST_F(CommonMpdBuilderTest, CheckVideoInfoVp8CodecInWebm) { - const char kTestMediaInfoCodecVp8[] = - "video_info {\n" - " codec: 'vp08.00.00.08.01.01.00.00'\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: 3\n"; - auto representation = - CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8), - kAnyRepresentationId, NoListener()); - ASSERT_TRUE(representation->Init()); - EXPECT_THAT(representation->GetXml().get(), AttributeEqual("codecs", "vp8")); -} - -// Check that vp9 codec string will be updated for backward compatibility -// support in webm. -TEST_F(CommonMpdBuilderTest, CheckVideoInfoVp9CodecInWebm) { - const char kTestMediaInfoCodecVp9[] = - "video_info {\n" - " codec: 'vp09.00.00.08.01.01.00.00'\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: 3\n"; - auto representation = - CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp9), - kAnyRepresentationId, NoListener()); - ASSERT_TRUE(representation->Init()); - EXPECT_THAT(representation->GetXml().get(), AttributeEqual("codecs", "vp9")); -} - -// Make sure RepresentationStateChangeListener::OnNewSegmentForRepresentation() -// is called. -TEST_F(CommonMpdBuilderTest, - RepresentationStateChangeListenerOnNewSegmentForRepresentation) { - const char kTestMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 720\n" - " height: 480\n" - " time_scale: 10\n" - " frame_duration: 10\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - - const uint64_t kStartTime = 199238u; - const uint64_t kDuration = 98u; - std::unique_ptr listener( - new MockRepresentationStateChangeListener()); - EXPECT_CALL(*listener, OnNewSegmentForRepresentation(kStartTime, kDuration)); - auto representation = - CreateRepresentation(ConvertToMediaInfo(kTestMediaInfo), - kAnyRepresentationId, std::move(listener)); - EXPECT_TRUE(representation->Init()); - - representation->AddNewSegment(kStartTime, kDuration, 10 /* any size */); -} - -// Make sure -// RepresentationStateChangeListener::OnSetFrameRateForRepresentation() -// is called. -TEST_F(CommonMpdBuilderTest, - RepresentationStateChangeListenerOnSetFrameRateForRepresentation) { - const char kTestMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 720\n" - " height: 480\n" - " time_scale: 1000\n" - " frame_duration: 10\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - - const uint64_t kTimeScale = 1000u; - const uint64_t kFrameDuration = 33u; - std::unique_ptr listener( - new MockRepresentationStateChangeListener()); - EXPECT_CALL(*listener, - OnSetFrameRateForRepresentation(kFrameDuration, kTimeScale)); - auto representation = - CreateRepresentation(ConvertToMediaInfo(kTestMediaInfo), - kAnyRepresentationId, std::move(listener)); - EXPECT_TRUE(representation->Init()); - - representation->SetSampleDuration(kFrameDuration); -} - -// Verify that content type is set correctly if video info is present in -// MediaInfo. -TEST_F(CommonMpdBuilderTest, CheckAdaptationSetVideoContentType) { - 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: CONTAINER_MP4\n"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo)); - EXPECT_THAT(adaptation_set->GetXml().get(), - AttributeEqual("contentType", "video")); -} - -// Verify that content type is set correctly if audio info is present in -// MediaInfo. -TEST_F(CommonMpdBuilderTest, CheckAdaptationSetAudioContentType) { - 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: CONTAINER_MP4\n"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo)); - EXPECT_THAT(adaptation_set->GetXml().get(), - AttributeEqual("contentType", "audio")); -} - -// Verify that content type is set correctly if text info is present in -// MediaInfo. -TEST_F(CommonMpdBuilderTest, CheckAdaptationSetTextContentType) { - const char kTextMediaInfo[] = - "text_info {\n" - " format: 'ttml'\n" - " language: 'en'\n" - "}\n" - "container_type: CONTAINER_TEXT\n"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "en"); - adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo)); - EXPECT_THAT(adaptation_set->GetXml().get(), - AttributeEqual("contentType", "text")); -} - -TEST_F(CommonMpdBuilderTest, TtmlXmlMimeType) { - const char kTtmlXmlMediaInfo[] = - "text_info {\n" - " format: 'ttml'\n" - "}\n" - "container_type: CONTAINER_TEXT\n"; - - auto representation = - CreateRepresentation(ConvertToMediaInfo(kTtmlXmlMediaInfo), - kAnyRepresentationId, NoListener()); - ASSERT_TRUE(representation->Init()); - EXPECT_THAT(representation->GetXml().get(), - AttributeEqual("mimeType", "application/ttml+xml")); -} - -TEST_F(CommonMpdBuilderTest, TtmlMp4MimeType) { - const char kTtmlMp4MediaInfo[] = - "text_info {\n" - " format: 'ttml'\n" - "}\n" - "container_type: CONTAINER_MP4\n"; - - auto representation = - CreateRepresentation(ConvertToMediaInfo(kTtmlMp4MediaInfo), - kAnyRepresentationId, NoListener()); - ASSERT_TRUE(representation->Init()); - EXPECT_THAT(representation->GetXml().get(), - AttributeEqual("mimeType", "application/mp4")); -} - -TEST_F(CommonMpdBuilderTest, WebVttMimeType) { - const char kWebVttMediaInfo[] = - "text_info {\n" - " format: 'vtt'\n" - "}\n" - "container_type: CONTAINER_TEXT\n"; - - auto representation = CreateRepresentation( - ConvertToMediaInfo(kWebVttMediaInfo), kAnyRepresentationId, NoListener()); - ASSERT_TRUE(representation->Init()); - EXPECT_THAT(representation->GetXml().get(), - AttributeEqual("mimeType", "text/vtt")); -} - -// Verify that language passed to the constructor sets the @lang field is set. -TEST_F(CommonMpdBuilderTest, CheckLanguageAttributeSet) { - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "en"); - EXPECT_THAT(adaptation_set->GetXml().get(), AttributeEqual("lang", "en")); -} - -// Verify that language tags with subtags can still be converted. -TEST_F(CommonMpdBuilderTest, CheckConvertLanguageWithSubtag) { - // "por-BR" is the long tag for Brazillian Portuguese. The short tag - // is "pt-BR", which is what should appear in the manifest. - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "por-BR"); - EXPECT_THAT(adaptation_set->GetXml().get(), AttributeEqual("lang", "pt-BR")); -} - -TEST_F(CommonMpdBuilderTest, CheckAdaptationSetId) { - const uint32_t kAdaptationSetId = 42; - auto adaptation_set = CreateAdaptationSet(kAdaptationSetId, kNoLanguage); - EXPECT_THAT(adaptation_set->GetXml().get(), - AttributeEqual("id", std::to_string(kAdaptationSetId))); -} - -// Verify AdaptationSet::AddRole() works for "main" role. -TEST_F(CommonMpdBuilderTest, AdaptationAddRoleElementMain) { - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - adaptation_set->AddRole(AdaptationSet::kRoleMain); - - // The empty contentType is sort of a side effect of being able to generate an - // MPD without adding any Representations. - const char kExpectedOutput[] = - "\n" - " \n" - ""; - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); -} - -// Add Role, ContentProtection, and Representation elements. Verify that -// ContentProtection -> Role -> Representation are in order. -TEST_F(CommonMpdBuilderTest, CheckContentProtectionRoleRepresentationOrder) { - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - adaptation_set->AddRole(AdaptationSet::kRoleMain); - ContentProtectionElement any_content_protection; - any_content_protection.scheme_id_uri = "any_scheme"; - adaptation_set->AddContentProtectionElement(any_content_protection); - 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"; - adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo)); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - const char kExpectedOutput[] = - "\n" - " \n" - " \n" - " \n" - " \n" - " \n" - ""; - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); -} - -// 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"; - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo1))); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("frameRate", "10/3")); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxFrameRate"))); -} - -// 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"; - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE(adaptation_set->AddRepresentation( - ConvertToMediaInfo(kVideoMediaInfo30fps))); - ASSERT_TRUE(adaptation_set->AddRepresentation( - ConvertToMediaInfo(kVideoMediaInfo15fps))); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), - AttributeEqual("maxFrameRate", "3000/100")); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate"))); -} - -// Verify that (max)FrameRate can be set by calling -// Representation::SetSampleDuration(). -TEST_F(CommonMpdBuilderTest, - SetAdaptationFrameRateUsingRepresentationSetSampleDuration) { - // Note that frame duration is not set in the MediaInfos. It could be there - // and should not affect the behavior of the program. - // But to make it closer to a real live-profile use case, - // the frame duration is not set in the MediaInfo, instead it is set using - // SetSampleDuration(). - const char k480pMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 720\n" - " height: 480\n" - " time_scale: 10\n" - " pixel_width: 8\n" - " pixel_height: 9\n" - "}\n" - "container_type: 1\n"; - const char k360pMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 640\n" - " height: 360\n" - " time_scale: 10\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - Representation* representation_480p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); - Representation* representation_360p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - - // First, make sure that maxFrameRate nor frameRate are set because - // frame durations were not provided in the MediaInfo. - xml::scoped_xml_ptr no_frame_rate(adaptation_set->GetXml()); - EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("maxFrameRate"))); - EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("frameRate"))); - - // Then set same frame duration for the representations. (Given that the - // time scales match). - const uint32_t kSameFrameDuration = 3u; - representation_480p->SetSampleDuration(kSameFrameDuration); - representation_360p->SetSampleDuration(kSameFrameDuration); - - xml::scoped_xml_ptr same_frame_rate(adaptation_set->GetXml()); - EXPECT_THAT(same_frame_rate.get(), Not(AttributeSet("maxFrameRate"))); - EXPECT_THAT(same_frame_rate.get(), AttributeEqual("frameRate", "10/3")); - - // Then set 480p to be 5fps (10/2) so that maxFrameRate is set. - const uint32_t k5FPSFrameDuration = 2; - static_assert(k5FPSFrameDuration < kSameFrameDuration, - "frame_duration_must_be_shorter_for_max_frame_rate"); - representation_480p->SetSampleDuration(k5FPSFrameDuration); - - xml::scoped_xml_ptr max_frame_rate(adaptation_set->GetXml()); - EXPECT_THAT(max_frame_rate.get(), AttributeEqual("maxFrameRate", "10/2")); - EXPECT_THAT(max_frame_rate.get(), Not(AttributeSet("frameRate"))); -} - -// Verify that if the picture aspect ratio of all the Representations are the -// same, @par attribute is present. -TEST_F(CommonMpdBuilderTest, AdaptationSetParAllSame) { - const char k480pVideoInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 854\n" - " height: 480\n" - " time_scale: 3000\n" - " frame_duration: 100\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - 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"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pVideoInfo))); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(k720pVideoInfo))); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(k1080pVideoInfo))); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pVideoInfo))); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("par", "16:9")); -} - -// 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"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(k16by9VideoInfo))); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(k2by1VideoInfo))); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("par"))); -} - -// 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"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE(adaptation_set->AddRepresentation( - ConvertToMediaInfo(kUknownPixelWidthAndHeight))); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("par"))); -} - -// 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"; - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo1))); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxFrameRate", "11/3")); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate"))); -} - -// Verify that Suppress*() methods work. -TEST_F(CommonMpdBuilderTest, SuppressRepresentationAttributes) { - 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"; - - auto representation = CreateRepresentation( - ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); - - representation->SuppressOnce(Representation::kSuppressWidth); - xml::scoped_xml_ptr no_width(representation->GetXml()); - EXPECT_THAT(no_width.get(), Not(AttributeSet("width"))); - EXPECT_THAT(no_width.get(), AttributeEqual("height", "480")); - EXPECT_THAT(no_width.get(), AttributeEqual("frameRate", "10/10")); - - representation->SuppressOnce(Representation::kSuppressHeight); - xml::scoped_xml_ptr no_height(representation->GetXml()); - EXPECT_THAT(no_height.get(), Not(AttributeSet("height"))); - EXPECT_THAT(no_height.get(), AttributeEqual("width", "720")); - EXPECT_THAT(no_height.get(), AttributeEqual("frameRate", "10/10")); - - representation->SuppressOnce(Representation::kSuppressFrameRate); - xml::scoped_xml_ptr no_frame_rate(representation->GetXml()); - EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("frameRate"))); - EXPECT_THAT(no_frame_rate.get(), AttributeEqual("width", "720")); - EXPECT_THAT(no_frame_rate.get(), AttributeEqual("height", "480")); -} - -// Attribute values that are common to all the children Representations should -// propagate up to AdaptationSet. Otherwise, each Representation should have -// its own values. -TEST_F(CommonMpdBuilderTest, BubbleUpAttributesToAdaptationSet) { - const char k1080p[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 1920\n" - " height: 1080\n" - " time_scale: 30\n" - " frame_duration: 1\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - - // Different width from the one above. - const char kDifferentWidth[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 1080\n" - " height: 1080\n" - " time_scale: 30\n" - " frame_duration: 1\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - - // Different height from ones above - const char kDifferentHeight[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 1440\n" - " height: 900\n" - " time_scale: 30\n" - " frame_duration: 1\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - - const char kDifferentFrameRate[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 1920\n" - " height: 1080\n" - " time_scale: 15\n" - " frame_duration: 1\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE(adaptation_set->AddRepresentation(ConvertToMediaInfo(k1080p))); - - xml::scoped_xml_ptr all_attributes_on_adaptation_set( - adaptation_set->GetXml()); - EXPECT_THAT(all_attributes_on_adaptation_set.get(), - AttributeEqual("width", "1920")); - EXPECT_THAT(all_attributes_on_adaptation_set.get(), - AttributeEqual("height", "1080")); - EXPECT_THAT(all_attributes_on_adaptation_set.get(), - AttributeEqual("frameRate", "30/1")); - - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentWidth))); - xml::scoped_xml_ptr width_not_set(adaptation_set->GetXml()); - EXPECT_THAT(width_not_set.get(), Not(AttributeSet("width"))); - EXPECT_THAT(width_not_set.get(), AttributeEqual("height", "1080")); - EXPECT_THAT(width_not_set.get(), AttributeEqual("frameRate", "30/1")); - - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(kDifferentHeight))); - xml::scoped_xml_ptr width_height_not_set(adaptation_set->GetXml()); - EXPECT_THAT(width_height_not_set.get(), Not(AttributeSet("width"))); - EXPECT_THAT(width_height_not_set.get(), Not(AttributeSet("height"))); - EXPECT_THAT(width_height_not_set.get(), AttributeEqual("frameRate", "30/1")); - - ASSERT_TRUE(adaptation_set->AddRepresentation( - ConvertToMediaInfo(kDifferentFrameRate))); - xml::scoped_xml_ptr no_common_attributes(adaptation_set->GetXml()); - EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("width"))); - EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("height"))); - EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("frameRate"))); -} - -// Verify that subsegmentAlignment is set to true if all the Representations' -// segments are aligned and the MPD type is static. -// Also checking that not all Representations have to be added before calling -// AddNewSegment() on a Representation. -TEST_F(OnDemandMpdBuilderTest, SubsegmentAlignment) { - const char k480pMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 720\n" - " height: 480\n" - " time_scale: 10\n" - " frame_duration: 10\n" - " pixel_width: 8\n" - " pixel_height: 9\n" - "}\n" - "container_type: 1\n"; - const char k360pMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 640\n" - " height: 360\n" - " time_scale: 10\n" - " frame_duration: 10\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - - // First use same start time and duration, and verify that subsegmentAlignment - // is set to true. - const uint64_t kStartTime = 0u; - const uint64_t kDuration = 10u; - const uint64_t kAnySize = 19834u; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - Representation* representation_480p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); - // Add a subsegment immediately before adding the 360p Representation. - // This should still work for VOD. - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); - - Representation* representation_360p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); - - xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); - EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true")); - - // Unknown because 480p has an extra subsegments. - representation_480p->AddNewSegment(11, 20, kAnySize); - xml::scoped_xml_ptr alignment_unknown(adaptation_set->GetXml()); - EXPECT_THAT(alignment_unknown.get(), - Not(AttributeSet("subsegmentAlignment"))); - - // Add segments that make them not aligned. - representation_360p->AddNewSegment(10, 1, kAnySize); - representation_360p->AddNewSegment(11, 19, kAnySize); - - xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); - EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment"))); -} - -// Verify that subsegmentAlignment can be force set to true. -TEST_F(OnDemandMpdBuilderTest, ForceSetsubsegmentAlignment) { - const char k480pMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 720\n" - " height: 480\n" - " time_scale: 10\n" - " frame_duration: 10\n" - " pixel_width: 8\n" - " pixel_height: 9\n" - "}\n" - "container_type: 1\n"; - const char k360pMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 640\n" - " height: 360\n" - " time_scale: 10\n" - " frame_duration: 10\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - Representation* representation_480p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); - Representation* representation_360p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - - // Use different starting times to make the segments "not aligned". - const uint64_t kStartTime1 = 1u; - const uint64_t kStartTime2 = 2u; - static_assert(kStartTime1 != kStartTime2, "StartTimesShouldBeDifferent"); - const uint64_t kDuration = 10u; - const uint64_t kAnySize = 19834u; - representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize); - representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize); - xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); - EXPECT_THAT(unaligned.get(), Not(AttributeSet("subsegmentAlignment"))); - - // Then force set the segment alignment attribute to true. - adaptation_set->ForceSetSegmentAlignment(true); - xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); - EXPECT_THAT(aligned.get(), AttributeEqual("subsegmentAlignment", "true")); -} - -// Verify that segmentAlignment is set to true if all the Representations -// segments' are aligned and the MPD type is dynamic. -TEST_F(LiveMpdBuilderTest, SegmentAlignment) { - const char k480pMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 720\n" - " height: 480\n" - " time_scale: 10\n" - " frame_duration: 10\n" - " pixel_width: 8\n" - " pixel_height: 9\n" - "}\n" - "container_type: 1\n"; - const char k360pMediaInfo[] = - "video_info {\n" - " codec: 'avc1'\n" - " width: 640\n" - " height: 360\n" - " time_scale: 10\n" - " frame_duration: 10\n" - " pixel_width: 1\n" - " pixel_height: 1\n" - "}\n" - "container_type: 1\n"; - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - Representation* representation_480p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); - Representation* representation_360p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - - // First use same start time and duration, and verify that that - // segmentAlignment is set. - const uint64_t kStartTime = 0u; - const uint64_t kDuration = 10u; - const uint64_t kAnySize = 19834u; - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); - representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); - xml::scoped_xml_ptr aligned(adaptation_set->GetXml()); - EXPECT_THAT(aligned.get(), AttributeEqual("segmentAlignment", "true")); - - // Add segments that make them not aligned. - representation_480p->AddNewSegment(11, 20, kAnySize); - representation_360p->AddNewSegment(10, 1, kAnySize); - representation_360p->AddNewSegment(11, 19, kAnySize); - - xml::scoped_xml_ptr unaligned(adaptation_set->GetXml()); - EXPECT_THAT(unaligned.get(), Not(AttributeSet("segmentAlignment"))); -} - -// Verify that the width and height attribute are set if all the video -// representations have the same width and height. -TEST_F(OnDemandMpdBuilderTest, 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"; - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo1))); - ASSERT_TRUE( - adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo2))); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("width", "1280")); - EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("height", "720")); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxWidth"))); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("maxHeight"))); -} - -// Verify that the maxWidth and maxHeight attribute are set if there are -// multiple video resolutions. -TEST_F(OnDemandMpdBuilderTest, AdaptationSetMaxWidthAndMaxHeight) { - 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"; - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE(adaptation_set->AddRepresentation( - ConvertToMediaInfo(kVideoMediaInfo1080p))); - ASSERT_TRUE(adaptation_set->AddRepresentation( - ConvertToMediaInfo(kVideoMediaInfo720p))); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxWidth", "1920")); - EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("maxHeight", "1080")); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("width"))); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("height"))); -} - -TEST_F(CommonMpdBuilderTest, CheckRepresentationId) { - const MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1); - const uint32_t kRepresentationId = 1; - - auto representation = - CreateRepresentation(video_media_info, kRepresentationId, NoListener()); - EXPECT_TRUE(representation->Init()); - EXPECT_THAT(representation->GetXml().get(), - AttributeEqual("id", std::to_string(kRepresentationId))); -} - -// Verify that Representation::SetSampleDuration() works by checking that -// AdaptationSet@frameRate is in the XML. -TEST_F(CommonMpdBuilderTest, SetSampleDuration) { - // Omit frame_duration so that SetSampleDuration() will set a new frameRate. - const char kVideoMediaInfo[] = - "video_info {\n" - " codec: \"avc1\"\n" - " width: 1920\n" - " height: 1080\n" - " time_scale: 3000\n" - "}\n" - "container_type: 1\n"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - - const MediaInfo video_media_info = ConvertToMediaInfo(kVideoMediaInfo); - Representation* representation = - adaptation_set->AddRepresentation(video_media_info); - EXPECT_TRUE(representation->Init()); - - xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); - EXPECT_THAT(adaptation_set_xml.get(), Not(AttributeSet("frameRate"))); - - representation->SetSampleDuration(2u); - adaptation_set_xml = adaptation_set->GetXml(); - EXPECT_THAT(adaptation_set_xml.get(), AttributeEqual("frameRate", "3000/2")); -} - -// Verify that AdaptationSet::AddContentProtection() and -// UpdateContentProtectionPssh() works. -TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) { - 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 = - "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; - content_protection.value = "some value"; - Element pssh; - pssh.name = "cenc:pssh"; - pssh.content = "any value"; - content_protection.subelements.push_back(pssh); - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE(adaptation_set->AddRepresentation( - ConvertToMediaInfo(kVideoMediaInfo1080p))); - adaptation_set->AddContentProtectionElement(content_protection); - - const char kExpectedOutput1[] = - "" - " " - " any value" - " " - " " - ""; - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput1)); - - adaptation_set->UpdateContentProtectionPssh( - "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "new pssh value"); - const char kExpectedOutput2[] = - "" - " " - // TODO(rkuroiwa): Commenting this out for now because we want to remove - // the PSSH from the MPD. Uncomment this when the player supports updating - // pssh. - //" new pssh value" - " " - " " - ""; - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput2)); -} - -// Verify that if the ContentProtection element for the DRM without -// element is updated via UpdateContentProtectionPssh(), the element gets added. -// TODO(rkuroiwa): Until the player supports PSSH update, we remove the pssh -// element. Rename this test once it is supported. -TEST_F(CommonMpdBuilderTest, UpdateToRemovePsshElement) { - 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 = - "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; - content_protection.value = "some value"; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - ASSERT_TRUE(adaptation_set->AddRepresentation( - ConvertToMediaInfo(kVideoMediaInfo1080p))); - adaptation_set->AddContentProtectionElement(content_protection); - - const char kExpectedOutput1[] = - "" - " " - " " - " " - ""; - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput1)); - - adaptation_set->UpdateContentProtectionPssh( - "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "added pssh value"); - const char kExpectedOutput2[] = - "" - " " - // TODO(rkuroiwa): Commenting this out for now because we want to remove - // teh PSSH from the MPD. Uncomment this when the player supports updating - // pssh. - //" added pssh value" - " " - " " - ""; - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput2)); -} - // Add one video check the output. TEST_F(OnDemandMpdBuilderTest, Video) { MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1); @@ -1605,66 +368,6 @@ TEST_F(OnDemandMpdBuilderTest, VideoAndAudio) { EXPECT_NO_FATAL_FAILURE(CheckMpd(kFileNameExpectedMpdOutputAudio1AndVideo1)); } -// MPD schema has strict ordering. AudioChannelConfiguration must appear before -// ContentProtection. -// Also test that Representation::AddContentProtection() works. -TEST_F(OnDemandMpdBuilderTest, 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[] = - "" - " " - " " - " " - " anything" - " " - " encrypted_audio.mp4" - " " - " " - " " - " " - ""; - - 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); - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - Representation* audio_representation = - adaptation_set->AddRepresentation(ConvertToMediaInfo(kTestMediaInfo)); - ASSERT_TRUE(audio_representation); - audio_representation->AddContentProtectionElement(content_protection); - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); -} - // Static profile requires bandwidth to be set because it has no other way to // get the bandwidth for the Representation. TEST_F(OnDemandMpdBuilderTest, MediaInfoMissingBandwidth) { @@ -1676,39 +379,6 @@ TEST_F(OnDemandMpdBuilderTest, MediaInfoMissingBandwidth) { ASSERT_FALSE(mpd_.ToString(&mpd_doc)); } -// Verify that a text path works. -TEST_F(OnDemandMpdBuilderTest, Text) { - const char kTextMediaInfo[] = - "text_info {\n" - " format: 'ttml'\n" - " language: 'en'\n" - " type: SUBTITLE\n" - "}\n" - "media_duration_seconds: 35\n" - "bandwidth: 1000\n" - "media_file_name: 'subtitle.xml'\n" - "container_type: CONTAINER_TEXT\n"; - - const char kExpectedOutput[] = - "" - " \n" - " " - " subtitle.xml" - " " - ""; - - auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, "en"); - Representation* text_representation = - adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo)); - ASSERT_TRUE(text_representation); - - EXPECT_THAT(adaptation_set->GetXml().get(), XmlNodeEqual(kExpectedOutput)); -} - // Check whether the attributes are set correctly for dynamic element. // This test must use ASSERT_EQ for comparison because XmlEqual() cannot // handle namespaces correctly yet. diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h index aac5af2b85..45ba671828 100644 --- a/packager/mpd/base/representation.h +++ b/packager/mpd/base/representation.h @@ -145,8 +145,7 @@ class Representation { Representation& operator=(const Representation&) = delete; friend class AdaptationSet; - template - friend class MpdBuilderTest; + friend class RepresentationTest; bool AddLiveInfo(xml::RepresentationXmlNode* representation); diff --git a/packager/mpd/base/representation_unittest.cc b/packager/mpd/base/representation_unittest.cc new file mode 100644 index 0000000000..f5449316be --- /dev/null +++ b/packager/mpd/base/representation_unittest.cc @@ -0,0 +1,369 @@ +// Copyright 2017 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 "packager/mpd/base/representation.h" + +#include +#include + +#include "packager/mpd/test/mpd_builder_test_helper.h" +#include "packager/mpd/test/xml_compare.h" + +using ::testing::Not; + +namespace shaka { +namespace { + +const uint32_t kAnyRepresentationId = 1; + +class MockRepresentationStateChangeListener + : public RepresentationStateChangeListener { + public: + MockRepresentationStateChangeListener() {} + ~MockRepresentationStateChangeListener() {} + + MOCK_METHOD2(OnNewSegmentForRepresentation, + void(uint64_t start_time, uint64_t duration)); + + MOCK_METHOD2(OnSetFrameRateForRepresentation, + void(uint32_t frame_duration, uint32_t timescale)); +}; + +} // namespace + +class RepresentationTest : public ::testing::Test { + public: + // TODO(rkuroiwa): Once std::forward() is allowed by chromium style guide, use + // variadic template and std::forward() so that we don't need to copy the + // constructor signatures. + std::unique_ptr CreateRepresentation( + const MediaInfo& media_info, + uint32_t representation_id, + std::unique_ptr + state_change_listener) { + return std::unique_ptr( + new Representation(media_info, mpd_options_, representation_id, + std::move(state_change_listener))); + } + + std::unique_ptr NoListener() { + return std::unique_ptr(); + } + + private: + MpdOptions mpd_options_; +}; + +// Verify that Representation::Init() works with all "required" fields of +// MedieInfo proto. +TEST_F(RepresentationTest, 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"; + + auto representation = CreateRepresentation( + ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); + EXPECT_TRUE(representation->Init()); +} + +// Verify that if VideoInfo, AudioInfo, or TextInfo is not set, Init() fails. +TEST_F(RepresentationTest, VideoAudioTextInfoNotSet) { + const char kTestMediaInfo[] = "container_type: 1"; + + auto representation = CreateRepresentation( + ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); + EXPECT_FALSE(representation->Init()); +} + +// Verify that if more than one of VideoInfo, AudioInfo, or TextInfo is set, +// then Init() fails. +TEST_F(RepresentationTest, VideoAndAudioInfoSet) { + 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" + "audio_info {\n" + " codec: 'mp4a.40.2'\n" + " sampling_frequency: 44100\n" + " time_scale: 1200\n" + " num_channels: 2\n" + "}\n" + "container_type: CONTAINER_MP4\n"; + + auto representation = CreateRepresentation( + ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); + EXPECT_FALSE(representation->Init()); +} + +// Verify that Representation::Init() fails if a required field is missing. +TEST_F(RepresentationTest, 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"; + auto representation = CreateRepresentation( + ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); + EXPECT_FALSE(representation->Init()); +} + +// Basic check that the fields in video info are in the XML. +TEST_F(RepresentationTest, 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"; + auto representation = CreateRepresentation( + ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); + ASSERT_TRUE(representation->Init()); + const char kExpectedOutput[] = + ""; + EXPECT_THAT(representation->GetXml().get(), XmlNodeEqual(kExpectedOutput)); +} + +TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInMp4) { + const char kTestMediaInfoCodecVp8[] = + "video_info {\n" + " codec: 'vp08.00.00.08.01.01.00.00'\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"; + auto representation = + CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8), + kAnyRepresentationId, NoListener()); + ASSERT_TRUE(representation->Init()); + EXPECT_THAT(representation->GetXml().get(), + AttributeEqual("codecs", "vp08.00.00.08.01.01.00.00")); +} + +// Check that vp8 codec string will be updated for backward compatibility +// support in webm. +TEST_F(RepresentationTest, CheckVideoInfoVp8CodecInWebm) { + const char kTestMediaInfoCodecVp8[] = + "video_info {\n" + " codec: 'vp08.00.00.08.01.01.00.00'\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: 3\n"; + auto representation = + CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp8), + kAnyRepresentationId, NoListener()); + ASSERT_TRUE(representation->Init()); + EXPECT_THAT(representation->GetXml().get(), AttributeEqual("codecs", "vp8")); +} + +// Check that vp9 codec string will be updated for backward compatibility +// support in webm. +TEST_F(RepresentationTest, CheckVideoInfoVp9CodecInWebm) { + const char kTestMediaInfoCodecVp9[] = + "video_info {\n" + " codec: 'vp09.00.00.08.01.01.00.00'\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: 3\n"; + auto representation = + CreateRepresentation(ConvertToMediaInfo(kTestMediaInfoCodecVp9), + kAnyRepresentationId, NoListener()); + ASSERT_TRUE(representation->Init()); + EXPECT_THAT(representation->GetXml().get(), AttributeEqual("codecs", "vp9")); +} + +// Make sure RepresentationStateChangeListener::OnNewSegmentForRepresentation() +// is called. +TEST_F(RepresentationTest, + RepresentationStateChangeListenerOnNewSegmentForRepresentation) { + const char kTestMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + const uint64_t kStartTime = 199238u; + const uint64_t kDuration = 98u; + std::unique_ptr listener( + new MockRepresentationStateChangeListener()); + EXPECT_CALL(*listener, OnNewSegmentForRepresentation(kStartTime, kDuration)); + auto representation = + CreateRepresentation(ConvertToMediaInfo(kTestMediaInfo), + kAnyRepresentationId, std::move(listener)); + EXPECT_TRUE(representation->Init()); + + representation->AddNewSegment(kStartTime, kDuration, 10 /* any size */); +} + +// Make sure +// RepresentationStateChangeListener::OnSetFrameRateForRepresentation() +// is called. +TEST_F(RepresentationTest, + RepresentationStateChangeListenerOnSetFrameRateForRepresentation) { + const char kTestMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 1000\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + const uint64_t kTimeScale = 1000u; + const uint64_t kFrameDuration = 33u; + std::unique_ptr listener( + new MockRepresentationStateChangeListener()); + EXPECT_CALL(*listener, + OnSetFrameRateForRepresentation(kFrameDuration, kTimeScale)); + auto representation = + CreateRepresentation(ConvertToMediaInfo(kTestMediaInfo), + kAnyRepresentationId, std::move(listener)); + EXPECT_TRUE(representation->Init()); + + representation->SetSampleDuration(kFrameDuration); +} + +TEST_F(RepresentationTest, TtmlXmlMimeType) { + const char kTtmlXmlMediaInfo[] = + "text_info {\n" + " format: 'ttml'\n" + "}\n" + "container_type: CONTAINER_TEXT\n"; + + auto representation = + CreateRepresentation(ConvertToMediaInfo(kTtmlXmlMediaInfo), + kAnyRepresentationId, NoListener()); + ASSERT_TRUE(representation->Init()); + EXPECT_THAT(representation->GetXml().get(), + AttributeEqual("mimeType", "application/ttml+xml")); +} + +TEST_F(RepresentationTest, TtmlMp4MimeType) { + const char kTtmlMp4MediaInfo[] = + "text_info {\n" + " format: 'ttml'\n" + "}\n" + "container_type: CONTAINER_MP4\n"; + + auto representation = + CreateRepresentation(ConvertToMediaInfo(kTtmlMp4MediaInfo), + kAnyRepresentationId, NoListener()); + ASSERT_TRUE(representation->Init()); + EXPECT_THAT(representation->GetXml().get(), + AttributeEqual("mimeType", "application/mp4")); +} + +TEST_F(RepresentationTest, WebVttMimeType) { + const char kWebVttMediaInfo[] = + "text_info {\n" + " format: 'vtt'\n" + "}\n" + "container_type: CONTAINER_TEXT\n"; + + auto representation = CreateRepresentation( + ConvertToMediaInfo(kWebVttMediaInfo), kAnyRepresentationId, NoListener()); + ASSERT_TRUE(representation->Init()); + EXPECT_THAT(representation->GetXml().get(), + AttributeEqual("mimeType", "text/vtt")); +} + +// Verify that Suppress*() methods work. +TEST_F(RepresentationTest, SuppressRepresentationAttributes) { + 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"; + + auto representation = CreateRepresentation( + ConvertToMediaInfo(kTestMediaInfo), kAnyRepresentationId, NoListener()); + + representation->SuppressOnce(Representation::kSuppressWidth); + xml::scoped_xml_ptr no_width(representation->GetXml()); + EXPECT_THAT(no_width.get(), Not(AttributeSet("width"))); + EXPECT_THAT(no_width.get(), AttributeEqual("height", "480")); + EXPECT_THAT(no_width.get(), AttributeEqual("frameRate", "10/10")); + + representation->SuppressOnce(Representation::kSuppressHeight); + xml::scoped_xml_ptr no_height(representation->GetXml()); + EXPECT_THAT(no_height.get(), Not(AttributeSet("height"))); + EXPECT_THAT(no_height.get(), AttributeEqual("width", "720")); + EXPECT_THAT(no_height.get(), AttributeEqual("frameRate", "10/10")); + + representation->SuppressOnce(Representation::kSuppressFrameRate); + xml::scoped_xml_ptr no_frame_rate(representation->GetXml()); + EXPECT_THAT(no_frame_rate.get(), Not(AttributeSet("frameRate"))); + EXPECT_THAT(no_frame_rate.get(), AttributeEqual("width", "720")); + EXPECT_THAT(no_frame_rate.get(), AttributeEqual("height", "480")); +} + +TEST_F(RepresentationTest, CheckRepresentationId) { + const MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1); + const uint32_t kRepresentationId = 1; + + auto representation = + CreateRepresentation(video_media_info, kRepresentationId, NoListener()); + EXPECT_TRUE(representation->Init()); + EXPECT_THAT(representation->GetXml().get(), + AttributeEqual("id", std::to_string(kRepresentationId))); +} + +} // namespace shaka diff --git a/packager/mpd/mpd.gyp b/packager/mpd/mpd.gyp index 910eb0102b..ef85a39617 100644 --- a/packager/mpd/mpd.gyp +++ b/packager/mpd/mpd.gyp @@ -84,9 +84,11 @@ 'target_name': 'mpd_unittest', 'type': '<(gtest_target_type)', 'sources': [ + 'base/adaptation_set_unittest.cc', 'base/bandwidth_estimator_unittest.cc', 'base/dash_iop_mpd_notifier_unittest.cc', 'base/mpd_builder_unittest.cc', + 'base/representation_unittest.cc', 'base/simple_mpd_notifier_unittest.cc', 'base/xml/xml_node_unittest.cc', 'test/mpd_builder_test_helper.cc',