#include "packager/media/formats/mp4/webvtt_fragmenter.h" #include #include #include "packager/base/strings/string_number_conversions.h" #include "packager/media/base/media_sample.h" #include "packager/media/base/test/status_test_util.h" namespace shaka { namespace media { namespace mp4 { namespace { // The actual messages don't matter. const char kCueMessage1[] = "hi"; const char kCueMessage2[] = "hello"; const char kCueMessage3[] = "some multi word message"; const char kCueMessage4[] = "message!!"; // Data is a vector and must not be empty. MATCHER_P3(MatchesStartTimeEndTimeAndData, start_time, end_time, data, "") { *result_listener << "which is (" << arg->pts() << ", " << (arg->pts() + arg->duration()) << ", " << base::HexEncode(arg->data(), arg->data_size()) << ")"; return arg->pts() == start_time && (arg->pts() + arg->duration() == end_time) && arg->data_size() == data.size() && (memcmp(&data[0], arg->data(), arg->data_size()) == 0); } } // namespace class WebVttFragmenterTest : public ::testing::Test { protected: WebVttFragmenter webvtt_fragmenter_; }; // Verify that AppednBoxToVector works. TEST_F(WebVttFragmenterTest, AppendBoxToVector) { const uint8_t kExpected[] = { 0x0, 0x0, 0x0, 0x1c, // Size. 0x76, 0x74, 0x74, 0x63, // 'vttc'. 0x0, 0x0, 0x0, 0x14, // Size of payload Box. 0x70, 0x61, 0x79, 0x6c, // 'payl'. // "some message" as hex without null terminator. 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, }; VTTCueBox cue_box; cue_box.cue_payload.cue_text = "some message"; std::vector serialized; AppendBoxToVector(&cue_box, &serialized); std::vector expected_in_vector_form( kExpected, kExpected + arraysize(kExpected)); EXPECT_EQ(expected_in_vector_form, serialized); } // There are 6 ways the cues can be arranged. // 1. No overlap, contiguous. Test: NoOverlapContiguous // |-- cue1 --| // |-- cue2 --| // // 2. No overlap, gap. Test: Gap // |-- cue1 --| // |-- cue2 --| // // 3. Overlap sequential (like a staircase). Test: OverlappingCuesSequential // |-- cue1 --| // |-- cue2 --| // |-- cue3 --| // // 4. Longer cues overlapping with shorter cues. Test: OverlappingLongCue // |---------- cue1 ----------| // |--- cue2 ---| // |- cue3 -| // |- cue4 -| // // 5. The first cue doesn't start at 00:00.000. Test: GapAtBeginning // |--- cue1 ---| // // 6. 2 or more cues start at the same time. Test: Same start time. // |--- cue1 ---| // |-- cue2 --| TEST_F(WebVttFragmenterTest, NoOverlapContiguous) { std::shared_ptr sample1 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage1), arraysize(kCueMessage1) - 1, true); sample1->set_pts(0); sample1->set_dts(0); sample1->set_duration(2000); webvtt_fragmenter_.PushSample(sample1); std::shared_ptr sample2 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage2), arraysize(kCueMessage2) - 1, true); sample2->set_pts(2000); sample2->set_dts(2000); sample2->set_duration(1000); webvtt_fragmenter_.PushSample(sample2); webvtt_fragmenter_.Flush(); EXPECT_EQ(2u, webvtt_fragmenter_.ReadySamplesSize()); VTTCueBox first_cue_data; first_cue_data.cue_payload.cue_text = kCueMessage1; std::vector expected; AppendBoxToVector(&first_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(0, 2000, expected)); VTTCueBox second_cue_data; second_cue_data.cue_payload.cue_text = kCueMessage2; expected.clear(); AppendBoxToVector(&second_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(2000, 3000, expected)); } // Verify that if is a gap, then a sample is created for the gap. TEST_F(WebVttFragmenterTest, Gap) { std::shared_ptr sample1 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage1), arraysize(kCueMessage1) - 1, true); sample1->set_pts(0); sample1->set_dts(0); sample1->set_duration(1000); webvtt_fragmenter_.PushSample(sample1); std::shared_ptr sample2 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage2), arraysize(kCueMessage2) - 1, true); sample2->set_pts(2000); sample2->set_dts(2000); sample2->set_duration(1000); webvtt_fragmenter_.PushSample(sample2); EXPECT_EQ(2u, webvtt_fragmenter_.ReadySamplesSize()); webvtt_fragmenter_.Flush(); EXPECT_EQ(3u, webvtt_fragmenter_.ReadySamplesSize()); VTTCueBox first_cue_data; first_cue_data.cue_payload.cue_text = kCueMessage1; std::vector expected; AppendBoxToVector(&first_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(0, 1000, expected)); VTTEmptyCueBox empty_cue; expected.clear(); AppendBoxToVector(&empty_cue, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(1000, 2000, expected)); VTTCueBox second_cue_data; second_cue_data.cue_payload.cue_text = kCueMessage2; expected.clear(); AppendBoxToVector(&second_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(2000, 3000, expected)); } // The previous cue always ends before the current cue ends. // Cues are overlapping, no samples should be created in PushSample(). TEST_F(WebVttFragmenterTest, OverlappingCuesSequential) { std::shared_ptr sample1 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage1), arraysize(kCueMessage1) - 1, true); sample1->set_pts(0); sample1->set_dts(0); sample1->set_duration(2000); webvtt_fragmenter_.PushSample(sample1); std::shared_ptr sample2 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage2), arraysize(kCueMessage2) - 1, true); sample2->set_pts(1000); sample2->set_dts(1000); sample2->set_duration(2000); webvtt_fragmenter_.PushSample(sample2); std::shared_ptr sample3 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage3), arraysize(kCueMessage3) - 1, true); sample3->set_pts(1500); sample3->set_dts(1500); sample3->set_duration(4000); webvtt_fragmenter_.PushSample(sample3); webvtt_fragmenter_.Flush(); // There should be 5 samples for [0,1000], [1000,1500], [1500,2000], // [2000,3000], and [3000, 5500]. EXPECT_EQ(5u, webvtt_fragmenter_.ReadySamplesSize()); VTTCueBox first_cue_data; first_cue_data.cue_payload.cue_text = kCueMessage1; std::vector expected; AppendBoxToVector(&first_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(0, 1000, expected)); VTTCueBox second_cue_data; second_cue_data.cue_payload.cue_text = kCueMessage2; expected.clear(); AppendBoxToVector(&first_cue_data, &expected); AppendBoxToVector(&second_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(1000, 1500, expected)); VTTCueBox third_cue_data; third_cue_data.cue_payload.cue_text = kCueMessage3; expected.clear(); AppendBoxToVector(&first_cue_data, &expected); AppendBoxToVector(&second_cue_data, &expected); AppendBoxToVector(&third_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(1500, 2000, expected)); expected.clear(); AppendBoxToVector(&second_cue_data, &expected); AppendBoxToVector(&third_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(2000, 3000, expected)); expected.clear(); AppendBoxToVector(&third_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(3000, 5500, expected)); } TEST_F(WebVttFragmenterTest, OverlappingLongCue) { std::shared_ptr sample1 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage1), arraysize(kCueMessage1) - 1, true); sample1->set_pts(0); sample1->set_dts(0); sample1->set_duration(10000); webvtt_fragmenter_.PushSample(sample1); std::shared_ptr sample2 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage2), arraysize(kCueMessage2) - 1, true); sample2->set_pts(1000); sample2->set_dts(1000); sample2->set_duration(5000); webvtt_fragmenter_.PushSample(sample2); std::shared_ptr sample3 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage3), arraysize(kCueMessage3) - 1, true); sample3->set_pts(2000); sample3->set_dts(2000); sample3->set_duration(1000); webvtt_fragmenter_.PushSample(sample3); std::shared_ptr sample4 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage4), arraysize(kCueMessage4) - 1, true); sample4->set_pts(8000); sample4->set_dts(8000); sample4->set_duration(1000); webvtt_fragmenter_.PushSample(sample4); webvtt_fragmenter_.Flush(); // There should be 7 samples for [0,1000], [1000,2000], [2000,3000], // [3000,6000], [6000, 8000], [8000, 9000], [9000, 10000]. EXPECT_EQ(7u, webvtt_fragmenter_.ReadySamplesSize()); VTTCueBox first_long_cue_data; first_long_cue_data.cue_payload.cue_text = kCueMessage1; std::vector expected; AppendBoxToVector(&first_long_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(0, 1000, expected)); VTTCueBox second_cue_data; second_cue_data.cue_payload.cue_text = kCueMessage2; expected.clear(); AppendBoxToVector(&first_long_cue_data, &expected); AppendBoxToVector(&second_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(1000, 2000, expected)); VTTCueBox third_cue_data; third_cue_data.cue_payload.cue_text = kCueMessage3; expected.clear(); AppendBoxToVector(&first_long_cue_data, &expected); AppendBoxToVector(&second_cue_data, &expected); AppendBoxToVector(&third_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(2000, 3000, expected)); expected.clear(); AppendBoxToVector(&first_long_cue_data, &expected); AppendBoxToVector(&second_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(3000, 6000, expected)); expected.clear(); AppendBoxToVector(&first_long_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(6000, 8000, expected)); VTTCueBox fourth_cue_data; fourth_cue_data.cue_payload.cue_text = kCueMessage4; expected.clear(); AppendBoxToVector(&first_long_cue_data, &expected); AppendBoxToVector(&fourth_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(8000, 9000, expected)); expected.clear(); AppendBoxToVector(&first_long_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(9000, 10000, expected)); } TEST_F(WebVttFragmenterTest, GapAtBeginning) { std::shared_ptr sample1 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage1), arraysize(kCueMessage1) - 1, true); sample1->set_pts(1200); sample1->set_dts(1200); sample1->set_duration(2000); webvtt_fragmenter_.PushSample(sample1); webvtt_fragmenter_.Flush(); EXPECT_EQ(1u, webvtt_fragmenter_.ReadySamplesSize()); VTTCueBox cue_data; cue_data.cue_payload.cue_text = kCueMessage1; std::vector expected; AppendBoxToVector(&cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(1200, 3200, expected)); } TEST_F(WebVttFragmenterTest, SameStartTime) { // TODO(rkuroiwa): This should be std::shared_ptr if this is applied on HEAD. std::shared_ptr sample1 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage1), arraysize(kCueMessage1) - 1, true); sample1->set_pts(0); sample1->set_dts(0); sample1->set_duration(2000); webvtt_fragmenter_.PushSample(sample1); std::shared_ptr sample2 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage2), arraysize(kCueMessage2) - 1, true); sample2->set_pts(0); sample2->set_dts(0); sample2->set_duration(1500); webvtt_fragmenter_.PushSample(sample2); webvtt_fragmenter_.Flush(); EXPECT_EQ(2u, webvtt_fragmenter_.ReadySamplesSize()); VTTCueBox first_cue_data; first_cue_data.cue_payload.cue_text = kCueMessage1; VTTCueBox second_cue_data; second_cue_data.cue_payload.cue_text = kCueMessage2; std::vector expected; AppendBoxToVector(&first_cue_data, &expected); AppendBoxToVector(&second_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(0, 1500, expected)); expected.clear(); AppendBoxToVector(&first_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(1500, 2000, expected)); } // This test is a combination of the test cases above. TEST_F(WebVttFragmenterTest, MoreCases) { std::shared_ptr sample1 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage1), arraysize(kCueMessage1) - 1, true); sample1->set_pts(0); sample1->set_dts(0); sample1->set_duration(2000); webvtt_fragmenter_.PushSample(sample1); std::shared_ptr sample2 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage2), arraysize(kCueMessage2) - 1, true); sample2->set_pts(100); sample2->set_dts(100); sample2->set_duration(100); webvtt_fragmenter_.PushSample(sample2); std::shared_ptr sample3 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage3), arraysize(kCueMessage3) - 1, true); sample3->set_pts(1500); sample3->set_dts(1500); sample3->set_duration(1000); webvtt_fragmenter_.PushSample(sample3); std::shared_ptr sample4 = MediaSample::CopyFrom(reinterpret_cast(kCueMessage4), arraysize(kCueMessage4) - 1, true); sample4->set_pts(1500); sample4->set_dts(1500); sample4->set_duration(800); webvtt_fragmenter_.PushSample(sample4); webvtt_fragmenter_.Flush(); EXPECT_EQ(6u, webvtt_fragmenter_.ReadySamplesSize()); VTTCueBox first_cue_data; first_cue_data.cue_payload.cue_text = kCueMessage1; VTTCueBox second_cue_data; second_cue_data.cue_payload.cue_text = kCueMessage2; VTTCueBox third_cue_data; third_cue_data.cue_payload.cue_text = kCueMessage3; VTTCueBox fourth_cue_data; fourth_cue_data.cue_payload.cue_text = kCueMessage4; std::vector expected; AppendBoxToVector(&first_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(0, 100, expected)); expected.clear(); AppendBoxToVector(&first_cue_data, &expected); AppendBoxToVector(&second_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(100, 200, expected)); expected.clear(); AppendBoxToVector(&first_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(200, 1500, expected)); expected.clear(); AppendBoxToVector(&first_cue_data, &expected); AppendBoxToVector(&third_cue_data, &expected); AppendBoxToVector(&fourth_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(1500, 2000, expected)); expected.clear(); AppendBoxToVector(&third_cue_data, &expected); AppendBoxToVector(&fourth_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(2000, 2300, expected)); expected.clear(); AppendBoxToVector(&third_cue_data, &expected); EXPECT_THAT(webvtt_fragmenter_.PopSample(), MatchesStartTimeEndTimeAndData(2300, 2500, expected)); } } // namespace shaka } // namespace media } // namespace edash_packager