// 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 #include #include #include #include #include "packager/base/logging.h" #include "packager/base/strings/string_util.h" #include "packager/mpd/base/segment_info.h" #include "packager/mpd/base/xml/xml_node.h" #include "packager/mpd/test/mpd_builder_test_helper.h" #include "packager/mpd/test/xml_compare.h" DECLARE_bool(segment_template_constant_duration); DECLARE_bool(dash_add_last_segment_number_when_needed); using ::testing::ElementsAre; namespace shaka { namespace xml { namespace { // Template so that it works for ContentProtectionXml and // ContentProtectionXml::Element. template void AddAttribute(const std::string& name, const std::string& value, XmlElement* content_protection_xml) { MediaInfo::ContentProtectionXml::AttributeNameValuePair* attribute = content_protection_xml->add_attributes(); attribute->set_name(name); attribute->set_value(value); } } // namespace // Make sure XmlEqual() is functioning correctly. // TODO(rkuroiwa): Move this to a separate file. This requires it to be TEST // due to gtest /test TEST(XmlNodeTest, MetaTestXmlElementsEqual) { static const char kXml1[] = "\n" " \n" " \n" " \n" " \n" ""; // This is same as kXml1 but the attributes are reordered. Note that the // children are not reordered. static const char kXml1AttributeReorder[] = "\n" " " " \n" " \n" " \n" ""; // is before . static const char kXml1ChildrenReordered[] = "\n" " \n" " " " \n" " \n" ""; // is before . static const char kXml1RemovedAttributes[] = "\n" " " " \n" " \n" " \n" ""; static const char kXml2[] = "\n" " \n" ""; // In XML , , and mean the same thing. static const char kXml2DifferentSyntax[] = "\n" " \n" ""; static const char kXml2MoreDifferentSyntax[] = "\n" " \n" ""; // Identity. ASSERT_TRUE(XmlEqual(kXml1, kXml1)); // Equivalent. ASSERT_TRUE(XmlEqual(kXml1, kXml1AttributeReorder)); ASSERT_TRUE(XmlEqual(kXml2, kXml2DifferentSyntax)); ASSERT_TRUE(XmlEqual(kXml2, kXml2MoreDifferentSyntax)); // Different. ASSERT_FALSE(XmlEqual(kXml1, kXml2)); ASSERT_FALSE(XmlEqual(kXml1, kXml1ChildrenReordered)); ASSERT_FALSE(XmlEqual(kXml1, kXml1RemovedAttributes)); ASSERT_FALSE(XmlEqual(kXml1AttributeReorder, kXml1ChildrenReordered)); } // Verify that if contents are different, XmlEqual returns false. // This is to catch the case where just using xmlNodeGetContent() on elements // that have subelements don't quite work well. // xmlNodeGetContent() (for both s) will return "content1content2". // But if it is run on for the first XML, it will return "content1", but // for second XML will return "c". TEST(XmlNodeTest, MetaTestXmlEqualDifferentContent) { ASSERT_FALSE(XmlEqual( "content1content2", "content1content2")); } TEST(XmlNodeTest, ExtractReferencedNamespaces) { XmlNode grand_child_with_namespace("grand_ns:grand_child"); grand_child_with_namespace.SetContent("grand child content"); XmlNode child("child1"); child.SetContent("child1 content"); ASSERT_TRUE(child.AddChild(std::move(grand_child_with_namespace))); XmlNode child_with_namespace("child_ns:child2"); child_with_namespace.SetContent("child2 content"); XmlNode root("root"); ASSERT_TRUE(root.AddChild(std::move(child))); ASSERT_TRUE(root.AddChild(std::move(child_with_namespace))); EXPECT_THAT(root.ExtractReferencedNamespaces(), ElementsAre("child_ns", "grand_ns")); } TEST(XmlNodeTest, ExtractReferencedNamespacesFromAttributes) { XmlNode child("child"); ASSERT_TRUE(child.SetStringAttribute("child_attribute_ns:attribute", "child attribute value")); XmlNode root("root"); ASSERT_TRUE(root.AddChild(std::move(child))); ASSERT_TRUE(root.SetStringAttribute("root_attribute_ns:attribute", "root attribute value")); EXPECT_THAT(root.ExtractReferencedNamespaces(), ElementsAre("child_attribute_ns", "root_attribute_ns")); } // Verify that AddContentProtectionElements work. // xmlReadMemory() (used in XmlEqual()) doesn't like XML fragments that have // namespaces without context, e.g. element. // The MpdBuilderTests work because the MPD element has xmlns:cenc attribute. // Tests that have is in mpd_builder_unittest. TEST(XmlNodeTest, AddContentProtectionElements) { std::list content_protections; ContentProtectionElement content_protection_widevine; content_protection_widevine.scheme_id_uri = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; content_protection_widevine.value = "SOME bogus Widevine DRM version"; Element any_element; any_element.name = "AnyElement"; any_element.content = "any content"; content_protection_widevine.subelements.push_back(any_element); content_protections.push_back(content_protection_widevine); ContentProtectionElement content_protection_clearkey; content_protection_clearkey.scheme_id_uri = "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"; content_protections.push_back(content_protection_clearkey); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddContentProtectionElements(content_protections)); EXPECT_THAT( representation, XmlNodeEqual( "\n" " \n" " any content\n" " \n" " " " \n" "")); } TEST(XmlNodeTest, AddEC3AudioInfo) { MediaInfo::AudioInfo audio_info; audio_info.set_codec("ec-3"); audio_info.set_sampling_frequency(48000); audio_info.mutable_codec_specific_data()->set_channel_mask(0xF801); audio_info.mutable_codec_specific_data()->set_channel_mpeg_value( 0xFFFFFFFF); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddAudioInfo(audio_info)); EXPECT_THAT( representation, XmlNodeEqual( "\n" " \n" "\n")); } TEST(XmlNodeTest, AddEC3AudioInfoMPEGScheme) { MediaInfo::AudioInfo audio_info; audio_info.set_codec("ec-3"); audio_info.set_sampling_frequency(48000); audio_info.mutable_codec_specific_data()->set_channel_mask(0xF801); audio_info.mutable_codec_specific_data()->set_channel_mpeg_value(6); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddAudioInfo(audio_info)); EXPECT_THAT(representation, XmlNodeEqual("\n" " \n" "\n")); } TEST(XmlNodeTest, AddEC3AudioInfoMPEGSchemeJOC) { MediaInfo::AudioInfo audio_info; audio_info.set_codec("ec-3"); audio_info.set_sampling_frequency(48000); audio_info.mutable_codec_specific_data()->set_channel_mask(0xF801); audio_info.mutable_codec_specific_data()->set_channel_mpeg_value(6); audio_info.mutable_codec_specific_data()->set_ec3_joc_complexity(16); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddAudioInfo(audio_info)); EXPECT_THAT( representation, XmlNodeEqual( "\n" " \n" " \n" " \n" "\n")); } TEST(XmlNodeTest, AddAC4AudioInfo) { MediaInfo::AudioInfo audio_info; audio_info.set_codec("ac-4.02.01.02"); audio_info.set_sampling_frequency(48000); auto* codec_data = audio_info.mutable_codec_specific_data(); codec_data->set_channel_mpeg_value(0xFFFFFFFF); codec_data->set_channel_mask(0x0000C7); codec_data->set_ac4_ims_flag(false); codec_data->set_ac4_cbi_flag(false); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddAudioInfo(audio_info)); EXPECT_THAT( representation, XmlNodeEqual( "\n" " \n" "\n")); } TEST(XmlNodeTest, AddAC4AudioInfoMPEGScheme) { MediaInfo::AudioInfo audio_info; audio_info.set_codec("ac-4.02.01.00"); audio_info.set_sampling_frequency(48000); auto* codec_data = audio_info.mutable_codec_specific_data(); codec_data->set_channel_mpeg_value(2); codec_data->set_channel_mask(0x000001); codec_data->set_ac4_ims_flag(false); codec_data->set_ac4_cbi_flag(false); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddAudioInfo(audio_info)); EXPECT_THAT(representation, XmlNodeEqual("\n" " \n" "\n")); } TEST(XmlNodeTest, AddAC4AudioInfoMPEGSchemeIMS) { MediaInfo::AudioInfo audio_info; audio_info.set_codec("ac-4.02.02.00"); audio_info.set_sampling_frequency(48000); auto* codec_data = audio_info.mutable_codec_specific_data(); codec_data->set_channel_mpeg_value(2); codec_data->set_channel_mask(0x000001); codec_data->set_ac4_ims_flag(true); codec_data->set_ac4_cbi_flag(false); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddAudioInfo(audio_info)); EXPECT_THAT( representation, XmlNodeEqual("\n" " \n" " \n" "\n")); } class LiveSegmentTimelineTest : public ::testing::Test { protected: void SetUp() override { FLAGS_segment_template_constant_duration = true; media_info_.set_segment_template_url("$Number$.m4s"); } void TearDown() override { FLAGS_segment_template_constant_duration = false; } MediaInfo media_info_; }; TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) { const uint32_t kStartNumber = 1; const uint64_t kStartTime = 0; const uint64_t kDuration = 100; const uint64_t kRepeat = 9; std::list segment_infos = { {kStartTime, kDuration, kRepeat}, }; RepresentationXmlNode representation; ASSERT_TRUE( representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); EXPECT_THAT( representation, XmlNodeEqual("" " " "")); } TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) { const uint32_t kStartNumber = 1; const uint64_t kNonZeroStartTime = 500; const uint64_t kDuration = 100; const uint64_t kRepeat = 9; std::list segment_infos = { {kNonZeroStartTime, kDuration, kRepeat}, }; RepresentationXmlNode representation; ASSERT_TRUE( representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); EXPECT_THAT(representation, XmlNodeEqual( "" " " " " " " " " " " "")); } TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) { const uint32_t kStartNumber = 6; const uint64_t kNonZeroStartTime = 500; const uint64_t kDuration = 100; const uint64_t kRepeat = 9; std::list segment_infos = { {kNonZeroStartTime, kDuration, kRepeat}, }; RepresentationXmlNode representation; ASSERT_TRUE( representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); EXPECT_THAT( representation, XmlNodeEqual("" " " "")); } TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) { const uint32_t kStartNumber = 1; const uint64_t kStartTime1 = 0; const uint64_t kDuration1 = 100; const uint64_t kRepeat1 = 9; const uint64_t kStartTime2 = kStartTime1 + (kRepeat1 + 1) * kDuration1; const uint64_t kDuration2 = 200; const uint64_t kRepeat2 = 0; std::list segment_infos = { {kStartTime1, kDuration1, kRepeat1}, {kStartTime2, kDuration2, kRepeat2}, }; RepresentationXmlNode representation; ASSERT_TRUE( representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); EXPECT_THAT( representation, XmlNodeEqual("" " " "")); } TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) { const uint32_t kStartNumber = 1; const uint64_t kStartTime1 = 0; const uint64_t kDuration1 = 100; const uint64_t kRepeat1 = 9; const uint64_t kStartTime2 = kStartTime1 + (kRepeat1 + 1) * kDuration1; const uint64_t kDuration2 = 200; const uint64_t kRepeat2 = 1; std::list segment_infos = { {kStartTime1, kDuration1, kRepeat1}, {kStartTime2, kDuration2, kRepeat2}, }; RepresentationXmlNode representation; ASSERT_TRUE( representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); EXPECT_THAT(representation, XmlNodeEqual( "" " " " " " " " " " " " " "")); } TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) { const uint32_t kStartNumber = 1; const uint64_t kStartTime1 = 0; const uint64_t kDuration1 = 100; const uint64_t kRepeat1 = 9; const uint64_t kGap = 100; const uint64_t kStartTime2 = kGap + kStartTime1 + (kRepeat1 + 1) * kDuration1; const uint64_t kDuration2 = 200; const uint64_t kRepeat2 = 0; std::list segment_infos = { {kStartTime1, kDuration1, kRepeat1}, {kStartTime2, kDuration2, kRepeat2}, }; RepresentationXmlNode representation; ASSERT_TRUE( representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); EXPECT_THAT(representation, XmlNodeEqual( "" " " " " " " " " " " " " "")); } TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { const uint32_t kStartNumber = 1; const uint64_t kStartTime = 0; const uint64_t kDuration = 100; const uint64_t kRepeat = 9; std::list segment_infos = { {kStartTime, kDuration, kRepeat}, }; RepresentationXmlNode representation; FLAGS_dash_add_last_segment_number_when_needed = true; ASSERT_TRUE( representation.AddLiveOnlyInfo(media_info_, segment_infos, kStartNumber)); EXPECT_THAT( representation, XmlNodeEqual("" "" " " "")); FLAGS_dash_add_last_segment_number_when_needed = false; } // Creating a separate Test Suite for RepresentationXmlNode::AddVODOnlyInfo class OnDemandVODSegmentTest : public ::testing::Test { }; TEST_F(OnDemandVODSegmentTest, SegmentBase) { const char kTestMediaInfo[] = "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_url: 'encrypted_audio.mp4'\n" "media_duration_seconds: 24.009434\n" "reference_time_scale: 44100\n" "presentation_time_offset: 100\n"; const MediaInfo media_info = ConvertToMediaInfo(kTestMediaInfo); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, false, 100)); EXPECT_THAT(representation, XmlNodeEqual("" "encrypted_audio.mp4" "" "" "" "")); } TEST_F(OnDemandVODSegmentTest, TextInfoBaseUrl) { const char kTextMediaInfo[] = "text_info {\n" " codec: 'ttml'\n" " language: 'en'\n" " type: SUBTITLE\n" "}\n" "media_duration_seconds: 35\n" "bandwidth: 1000\n" "media_file_url: 'subtitle.xml'\n" "container_type: CONTAINER_TEXT\n"; const MediaInfo media_info = ConvertToMediaInfo(kTextMediaInfo); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, false, 100)); EXPECT_THAT(representation, XmlNodeEqual("" "subtitle.xml" "")); } TEST_F(OnDemandVODSegmentTest, TextInfoWithPresentationOffset) { const char kTextMediaInfo[] = "text_info {\n" " codec: 'ttml'\n" " language: 'en'\n" " type: SUBTITLE\n" "}\n" "media_duration_seconds: 35\n" "bandwidth: 1000\n" "media_file_url: 'subtitle.xml'\n" "container_type: CONTAINER_TEXT\n" "presentation_time_offset: 100\n"; const MediaInfo media_info = ConvertToMediaInfo(kTextMediaInfo); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, false, 100)); EXPECT_THAT(representation, XmlNodeEqual("" "" "" "" "")); } TEST_F(OnDemandVODSegmentTest, SegmentListWithoutUrls) { const char kTestMediaInfo[] = "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_url: 'encrypted_audio.mp4'\n" "media_duration_seconds: 24.009434\n" "reference_time_scale: 44100\n" "presentation_time_offset: 100\n"; const MediaInfo media_info = ConvertToMediaInfo(kTestMediaInfo); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, true, 100)); EXPECT_THAT( representation, XmlNodeEqual("" "encrypted_audio.mp4" "" "" "" "")); } TEST_F(OnDemandVODSegmentTest, SegmentUrlWithMediaRanges) { const char kTextMediaInfo[] = "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_url: 'encrypted_audio.mp4'\n" "media_duration_seconds: 24.009434\n" "reference_time_scale: 44100\n" "presentation_time_offset: 100\n" "subsegment_ranges {\n" " begin: 932\n" " end: 9999\n" "}\n" "subsegment_ranges {\n" " begin: 10000\n" " end: 11000\n" "}\n"; const MediaInfo media_info = ConvertToMediaInfo(kTextMediaInfo); RepresentationXmlNode representation; ASSERT_TRUE(representation.AddVODOnlyInfo(media_info, true, 100)); EXPECT_THAT( representation, XmlNodeEqual("" "encrypted_audio.mp4" "" "" "" "" "" "")); } } // namespace xml } // namespace shaka