diff --git a/packager/media/demuxer/demuxer.cc b/packager/media/demuxer/demuxer.cc index 7b4cc1ee7f..419ea5c4be 100644 --- a/packager/media/demuxer/demuxer.cc +++ b/packager/media/demuxer/demuxer.cc @@ -20,7 +20,6 @@ #include "packager/media/formats/mp2t/mp2t_media_parser.h" #include "packager/media/formats/mp4/mp4_media_parser.h" #include "packager/media/formats/webm/webm_media_parser.h" -#include "packager/media/formats/webvtt/webvtt_media_parser.h" #include "packager/media/formats/wvm/wvm_media_parser.h" namespace { @@ -200,8 +199,6 @@ Status Demuxer::InitializeParser() { case CONTAINER_WEBM: parser_.reset(new WebMMediaParser()); break; - case CONTAINER_WEBVTT: - parser_.reset(new WebVttMediaParser()); break; default: NOTIMPLEMENTED(); diff --git a/packager/media/formats/webvtt/webvtt.gyp b/packager/media/formats/webvtt/webvtt.gyp index 687720bc1f..0790b91b87 100644 --- a/packager/media/formats/webvtt/webvtt.gyp +++ b/packager/media/formats/webvtt/webvtt.gyp @@ -17,14 +17,10 @@ 'cue.h', 'text_readers.cc', 'text_readers.h', - 'webvtt_media_parser.cc', - 'webvtt_media_parser.h', 'webvtt_output_handler.cc', 'webvtt_output_handler.h', 'webvtt_parser.cc', 'webvtt_parser.h', - 'webvtt_sample_converter.cc', - 'webvtt_sample_converter.h', 'webvtt_segmenter.cc', 'webvtt_segmenter.h', 'webvtt_timestamp.cc', @@ -44,10 +40,8 @@ 'type': '<(gtest_target_type)', 'sources': [ 'text_readers_unittest.cc', - 'webvtt_media_parser_unittest.cc', 'webvtt_output_handler_unittest.cc', 'webvtt_parser_unittest.cc', - 'webvtt_sample_converter_unittest.cc', 'webvtt_segmenter_unittest.cc', 'webvtt_pipeline_unittest.cc', 'webvtt_timestamp_unittest.cc', diff --git a/packager/media/formats/webvtt/webvtt_media_parser.cc b/packager/media/formats/webvtt/webvtt_media_parser.cc deleted file mode 100644 index 615f03036a..0000000000 --- a/packager/media/formats/webvtt/webvtt_media_parser.cc +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2015 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/media/formats/webvtt/webvtt_media_parser.h" - -#include -#include - -#include "packager/base/logging.h" -#include "packager/base/strings/string_number_conversions.h" -#include "packager/base/strings/string_split.h" -#include "packager/base/strings/string_util.h" -#include "packager/media/base/macros.h" -#include "packager/media/base/media_sample.h" -#include "packager/media/base/text_stream_info.h" -#include "packager/media/formats/webvtt/webvtt_timestamp.h" - -namespace shaka { -namespace media { - -namespace { - -const bool kFlush = true; - -// There's only one track in a WebVTT file. -const int kTrackId = 0; - -const char kCR = 0x0D; -const char kLF = 0x0A; - -// Reads the first line from |data| and removes the line. Returns false if there -// isn't a line break. Sets |line| with the content of the first line without -// the line break. -bool ReadLine(std::string* data, std::string* line) { - if (data->size() == 0) { - return false; - } - size_t string_position = 0; - // Length of the line break mark. 1 for LF and CR, 2 for CRLF. - int line_break_length = 1; - bool found_line_break = false; - while (string_position < data->size()) { - if (data->at(string_position) == kLF) { - found_line_break = true; - break; - } - - if (data->at(string_position) == kCR) { - found_line_break = true; - if (string_position + 1 >= data->size()) - break; - - if (data->at(string_position + 1) == kLF) - line_break_length = 2; - break; - } - - ++string_position; - } - - if (!found_line_break) - return false; - - *line = data->substr(0, string_position); - data->erase(0, string_position + line_break_length); - return true; -} - -// Clears |settings| and 0s |start_time| and |duration| regardless of the -// parsing result. -bool ParseTimingAndSettingsLine(const std::string& line, - uint64_t* start_time, - uint64_t* duration, - std::string* settings) { - *start_time = 0; - *duration = 0; - settings->clear(); - std::vector entries = base::SplitString( - line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - if (entries.size() < 3) { - // The timing is time1 --> time3 so if there aren't 3 entries, this is parse - // error. - LOG(ERROR) << "Not enough tokens to be a timing " << line; - return false; - } - - if (entries[1] != "-->") { - LOG(ERROR) << "Cannot find an arrow at the right place " << line; - return false; - } - - const std::string& start_time_str = entries[0]; - if (!WebVttTimestampToMs(start_time_str, start_time)) { - LOG(ERROR) << "Failed to parse " << start_time_str << " in " << line; - return false; - } - - const std::string& end_time_str = entries[2]; - uint64_t end_time = 0; - if (!WebVttTimestampToMs(end_time_str, &end_time)) { - LOG(ERROR) << "Failed to parse " << end_time_str << " in " << line; - return false; - } - *duration = end_time - *start_time; - - entries.erase(entries.begin(), entries.begin() + 3); - *settings = base::JoinString(entries, " "); - return true; -} - -} // namespace - -WebVttMediaParser::WebVttMediaParser() - : state_(kHeader), sample_converter_(new WebVttSampleConverter()) {} -WebVttMediaParser::~WebVttMediaParser() {} - -void WebVttMediaParser::Init(const InitCB& init_cb, - const NewSampleCB& new_sample_cb, - KeySource* decryption_key_source) { - init_cb_ = init_cb; - new_sample_cb_ = new_sample_cb; -} - -bool WebVttMediaParser::Flush() { - // If not in one of these states just be ready for more data. - if (state_ != kCuePayload && state_ != kComment) - return true; - - if (!data_.empty()) { - // If it was in the middle of the payload and the stream finished, then this - // is an end of the payload. The rest of the data is part of the payload. - if (state_ == kCuePayload) { - current_cue_.payload += data_ + "\n"; - } else { - current_cue_.comment += data_ + "\n"; - } - data_.clear(); - } - - if (!ProcessCurrentCue(kFlush)) { - state_ = kParseError; - return false; - } - - state_ = kCueIdentifierOrTimingOrComment; - return true; -} - -bool WebVttMediaParser::Parse(const uint8_t* buf, int size) { - if (state_ == kParseError) { - LOG(WARNING) << "The parser is in an error state, ignoring input."; - return false; - } - - data_.insert(data_.end(), buf, buf + size); - - std::string line; - while (ReadLine(&data_, &line)) { - // Only kCueIdentifierOrTimingOrComment and kCueTiming states accept -->. - // Error otherwise. - const bool has_arrow = line.find("-->") != std::string::npos; - if (state_ == kCueTiming) { - if (!has_arrow) { - LOG(ERROR) << "Expected --> in: " << line; - state_ = kParseError; - return false; - } - } else if (state_ != kCueIdentifierOrTimingOrComment) { - if (has_arrow) { - LOG(ERROR) << "Unexpected --> in " << line; - state_ = kParseError; - return false; - } - } - - switch (state_) { - case kHeader: - // No check. This should be WEBVTT when this object was created. - header_.push_back(line); - state_ = kMetadata; - break; - case kMetadata: { - if (line.empty()) { - std::vector> streams; - // The resolution of timings are in milliseconds. - const int kTimescale = 1000; - - // The duration passed here is not very important. Also the whole file - // must be read before determining the real duration which doesn't - // work nicely with the current demuxer. - const int kDuration = 0; - - // There is no one metadata to determine what the language is. Parts - // of the text may be annotated as some specific language. - const char kLanguage[] = ""; - - const char kWebVttCodecString[] = "wvtt"; - streams.emplace_back( - new TextStreamInfo(kTrackId, kTimescale, kDuration, - kCodecWebVtt, kWebVttCodecString, - base::JoinString(header_, "\n"), - 0, // Not necessary. - 0, - kLanguage)); // Not necessary. - - init_cb_.Run(streams); - state_ = kCueIdentifierOrTimingOrComment; - break; - } - - header_.push_back(line); - break; - } - case kCueIdentifierOrTimingOrComment: { - // Note that there can be one or more line breaks before a cue starts; - // skip this line. - // Or the file could end without a new cue. - if (line.empty()) - break; - - if (!has_arrow) { - if (base::StartsWith(line, "NOTE", - base::CompareCase::INSENSITIVE_ASCII)) { - state_ = kComment; - current_cue_.comment += line + "\n"; - } else { - // A cue can start from a cue identifier. - // https://w3c.github.io/webvtt/#webvtt-cue-identifier - current_cue_.identifier = line; - // The next line must be a timing. - state_ = kCueTiming; - } - break; - } - - // No break statement if the line has an arrow; it should be a WebVTT - // timing, so fall thru. Setting state_ to kCueTiming so that the state - // always matches the case. - state_ = kCueTiming; - FALLTHROUGH_INTENDED; - } - case kCueTiming: { - DCHECK(has_arrow); - if (!ParseTimingAndSettingsLine(line, ¤t_cue_.start_time, - ¤t_cue_.duration, - ¤t_cue_.settings)) { - state_ = kParseError; - return false; - } - state_ = kCuePayload; - break; - } - case kCuePayload: { - if (line.empty()) { - state_ = kCueIdentifierOrTimingOrComment; - if (!ProcessCurrentCue(!kFlush)) { - state_ = kParseError; - return false; - } - break; - } - - current_cue_.payload += line + "\n"; - break; - } - case kComment: { - if (line.empty()) { - state_ = kCueIdentifierOrTimingOrComment; - if (!ProcessCurrentCue(!kFlush)) { - state_ = kParseError; - return false; - } - break; - } - - current_cue_.comment += line + "\n"; - break; - } - case kParseError: - NOTREACHED(); - return false; - } - } - - return true; -} - -void WebVttMediaParser::InjectWebVttSampleConvertForTesting( - std::unique_ptr converter) { - sample_converter_ = std::move(converter); -} - -bool WebVttMediaParser::ProcessCurrentCue(bool flush) { - sample_converter_->PushCue(current_cue_); - current_cue_ = Cue(); - if (flush) - sample_converter_->Flush(); - - while (sample_converter_->ReadySamplesSize() > 0) { - if (!new_sample_cb_.Run(kTrackId, sample_converter_->PopSample())) { - LOG(ERROR) << "New sample callback failed."; - return false; - } - } - return true; -} - -} // namespace media -} // namespace shaka diff --git a/packager/media/formats/webvtt/webvtt_media_parser.h b/packager/media/formats/webvtt/webvtt_media_parser.h deleted file mode 100644 index 4b64994de7..0000000000 --- a/packager/media/formats/webvtt/webvtt_media_parser.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2015 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 - -#ifndef PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_MEDIA_PARSER_H_ -#define PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_MEDIA_PARSER_H_ - -#include - -#include -#include -#include - -#include "packager/base/compiler_specific.h" -#include "packager/media/base/media_parser.h" -#include "packager/media/formats/webvtt/cue.h" -#include "packager/media/formats/webvtt/webvtt_sample_converter.h" - -namespace shaka { -namespace media { - -// WebVTT parser. -// The input may not be encrypted so decryption_key_source is ignored. -class WebVttMediaParser : public MediaParser { - public: - WebVttMediaParser(); - ~WebVttMediaParser() override; - - /// @name MediaParser implementation overrides. - /// @{ - void Init(const InitCB& init_cb, - const NewSampleCB& new_sample_cb, - KeySource* decryption_key_source) override; - bool Flush() override WARN_UNUSED_RESULT; - bool Parse(const uint8_t* buf, int size) override WARN_UNUSED_RESULT; - /// @} - - void InjectWebVttSampleConvertForTesting( - std::unique_ptr converter); - - private: - enum WebVttReadingState { - kHeader, - kMetadata, - kCueIdentifierOrTimingOrComment, - kCueTiming, - kCuePayload, - kComment, - kParseError, - }; - - // Sends current cue to sample converter, and dispatches any ready samples to - // the callback. - // current_cue_ is always cleared. - bool ProcessCurrentCue(bool flush); - - InitCB init_cb_; - NewSampleCB new_sample_cb_; - - // All the unprocessed data passed to this parser. - std::string data_; - - // The WEBVTT text + metadata header (global settings) for this webvtt. - // One element per line. - std::vector header_; - - // This is set to what the parser is expecting. For example, if the parse is - // expecting a kCueTiming, then the next line that it parses should be a - // WebVTT timing line or an empty line. - WebVttReadingState state_; - - Cue current_cue_; - - std::unique_ptr sample_converter_; - - DISALLOW_COPY_AND_ASSIGN(WebVttMediaParser); -}; - -} // namespace media -} // namespace shaka - -#endif // PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_MEDIA_PARSER_H_ diff --git a/packager/media/formats/webvtt/webvtt_media_parser_unittest.cc b/packager/media/formats/webvtt/webvtt_media_parser_unittest.cc deleted file mode 100644 index 335aeea805..0000000000 --- a/packager/media/formats/webvtt/webvtt_media_parser_unittest.cc +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright 2015 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 "packager/base/bind.h" -#include "packager/base/strings/string_number_conversions.h" -#include "packager/media/base/media_sample.h" -#include "packager/media/base/stream_info.h" -#include "packager/media/formats/mp4/box_definitions.h" -#include "packager/media/formats/webvtt/webvtt_media_parser.h" - -namespace shaka { -namespace media { - -using mp4::VTTCueBox; - -namespace { -// 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 - -typedef testing::MockFunction>& stream_info)> - MockInitCallback; -typedef testing::MockFunction< - bool(uint32_t track_id, const std::shared_ptr& media_sample)> - MockNewSampleCallback; - -using testing::AtLeast; -using testing::InSequence; -using testing::Return; -using testing::_; - -class WebVttMediaParserTest : public ::testing::Test { - public: - void InitializeParser() { - parser_.Init( - base::Bind(&MockInitCallback::Call, base::Unretained(&init_callback_)), - base::Bind(&MockNewSampleCallback::Call, - base::Unretained(&new_sample_callback_)), - nullptr); - } - - MockInitCallback init_callback_; - MockNewSampleCallback new_sample_callback_; - - WebVttMediaParser parser_; -}; - -TEST_F(WebVttMediaParserTest, Init) { - InitializeParser(); -} - -TEST_F(WebVttMediaParserTest, ParseOneCue) { - EXPECT_CALL(init_callback_, Call(_)); - - VTTCueBox cue_box; - cue_box.cue_payload.cue_text = "subtitle"; - std::vector expected; - AppendBoxToVector(&cue_box, &expected); - - EXPECT_CALL(new_sample_callback_, - Call(_, MatchesStartTimeEndTimeAndData(60000, 3600000, expected))) - .WillOnce(Return(true)); - - const char kWebVtt[] = - "WEBVTT\n" - "\n" - "00:01:00.000 --> 01:00:00.000\n" - "subtitle\n"; - InitializeParser(); - EXPECT_TRUE(parser_.Parse(reinterpret_cast(kWebVtt), - arraysize(kWebVtt) - 1)); - - EXPECT_TRUE(parser_.Flush()); -} - -// Verify that different types of line breaks work. -TEST_F(WebVttMediaParserTest, DifferentLineBreaks) { - EXPECT_CALL(init_callback_, Call(_)); - EXPECT_CALL(new_sample_callback_, Call(_, _)).WillOnce(Return(true)); - - const char kWebVtt[] = - "WEBVTT\r\n" - "\r\n" - "00:01:00.000 --> 01:00:00.000\n" - "subtitle\r"; - InitializeParser(); - EXPECT_TRUE(parser_.Parse(reinterpret_cast(kWebVtt), - arraysize(kWebVtt) - 1)); - - EXPECT_TRUE(parser_.Flush()); -} - -// Verify that a typical case with mulitple cues works. -TEST_F(WebVttMediaParserTest, ParseMultipleCues) { - EXPECT_CALL(init_callback_, Call(_)); - - - VTTCueBox first_cue_box; - first_cue_box.cue_payload.cue_text = "subtitle"; - - VTTCueBox second_cue_data; - second_cue_data.cue_payload.cue_text = "more subtitle"; - - VTTCueBox third_cue_data; - third_cue_data.cue_payload.cue_text = "more text"; - - std::vector expected; - AppendBoxToVector(&first_cue_box, &expected); - EXPECT_CALL(new_sample_callback_, - Call(_, MatchesStartTimeEndTimeAndData(1000, 2321, expected))) - .WillOnce(Return(true)); - - expected.clear(); - AppendBoxToVector(&first_cue_box, &expected); - AppendBoxToVector(&second_cue_data, &expected); - EXPECT_CALL(new_sample_callback_, - Call(_, MatchesStartTimeEndTimeAndData(2321, 5200, expected))) - .WillOnce(Return(true)); - - expected.clear(); - AppendBoxToVector(&second_cue_data, &expected); - EXPECT_CALL(new_sample_callback_, - Call(_, MatchesStartTimeEndTimeAndData(5200, 5800, expected))) - .WillOnce(Return(true)); - - expected.clear(); - AppendBoxToVector(&second_cue_data, &expected); - AppendBoxToVector(&third_cue_data, &expected); - EXPECT_CALL(new_sample_callback_, - Call(_, MatchesStartTimeEndTimeAndData(5800, 7000, expected))) - .WillOnce(Return(true)); - - expected.clear(); - AppendBoxToVector(&third_cue_data, &expected); - EXPECT_CALL(new_sample_callback_, - Call(_, MatchesStartTimeEndTimeAndData(7000, 8000, expected))) - .WillOnce(Return(true)); - - const char kWebVtt[] = - "WEBVTT\n" - "\n" - "00:00:01.000 --> 00:00:05.200\n" - "subtitle\n" - "\n" - "00:00:02.321 --> 00:00:07.000\n" - "more subtitle\n" - "\n" - "00:00:05.800 --> 00:00:08.000\n" - "more text\n" ; - InitializeParser(); - EXPECT_TRUE(parser_.Parse(reinterpret_cast(kWebVtt), - arraysize(kWebVtt) - 1)); - - EXPECT_TRUE(parser_.Flush()); -} - -MATCHER_P2(MatchesStartTimeAndDuration, start_time, duration, "") { - return arg->pts() == start_time && arg->duration() == duration; -} - -// Verify that the timing parsing is done correctly and gets the right start -// time and duration in milliseconds. -TEST_F(WebVttMediaParserTest, VerifyTimingParsing) { - EXPECT_CALL(init_callback_, Call(_)); - EXPECT_CALL(new_sample_callback_, - Call(_, MatchesStartTimeAndDuration(61004u, 204088u))) - .WillOnce(Return(true)); - const char kWebVtt[] = - "WEBVTT\n" - "\n" - "00:01:01.004 --> 00:04:25.092\n" - "subtitle"; - InitializeParser(); - EXPECT_TRUE(parser_.Parse(reinterpret_cast(kWebVtt), - arraysize(kWebVtt) - 1)); - - EXPECT_TRUE(parser_.Flush()); -} - -// Expect parse failure if hour part of the timestamp is too short. -TEST_F(WebVttMediaParserTest, MalformedHourTimestamp) { - EXPECT_CALL(new_sample_callback_, Call(_, _)).Times(0); - - const char kHourStringTooShort[] = - "WEBVTT\n" - "\n" - "0:01:01.004 --> 00:04:25.092\n" - "subtitle\n"; - InitializeParser(); - - EXPECT_FALSE( - parser_.Parse(reinterpret_cast(kHourStringTooShort), - arraysize(kHourStringTooShort) - 1)); -} - -// Each component of the timestamp is correct but contains spaces. -TEST_F(WebVttMediaParserTest, SpacesInTimestamp) { - EXPECT_CALL(new_sample_callback_, Call(_, _)).Times(0); - - const char kSpacesInTimestamp[] = - "WEBVTT\n" - "\n" - "0:01: 1.004 --> 0 :04:25.092\n" - "subtitle\n"; - InitializeParser(); - - EXPECT_FALSE( - parser_.Parse(reinterpret_cast(kSpacesInTimestamp), - arraysize(kSpacesInTimestamp) - 1)); -} - -MATCHER_P(MatchesPayload, payload, "") { - return arg.payload.front() == std::string(payload); -} - -// Verify that a sample can be created from multiple calls to Parse(), i.e. one -// Parse() is not a full sample. -TEST_F(WebVttMediaParserTest, PartialParse) { - EXPECT_CALL(init_callback_, Call(_)); - EXPECT_CALL(new_sample_callback_, Call(_, _)).Times(0); - - const char kWebVtt[] = - "WEBVTT\n" - "\n" - "00:01:01.004 --> 00:04:25.092\n" - "subtitle"; - InitializeParser(); - // Pass in the first 8 bytes, i.e. right before the first cue. - EXPECT_TRUE(parser_.Parse(reinterpret_cast(kWebVtt), 8)); - - EXPECT_CALL(new_sample_callback_, Call(_, _)).WillOnce(Return(true)); - EXPECT_TRUE(parser_.Parse(reinterpret_cast(kWebVtt) + 8, - arraysize(kWebVtt) - 1 - 8)); - - EXPECT_TRUE(parser_.Flush()); -} - -// Verify that metadata header with --> is rejected. -TEST_F(WebVttMediaParserTest, BadMetadataHeader) { - EXPECT_CALL(init_callback_, Call(_)).Times(0); - EXPECT_CALL(new_sample_callback_, Call(_, _)).Times(0); - - const char kBadWebVtt[] = - "WEBVTT\n" - "00:01:01.004 --> 00:04:25.092\n"; - InitializeParser(); - EXPECT_FALSE(parser_.Parse(reinterpret_cast(kBadWebVtt), - arraysize(kBadWebVtt) - 1)); - EXPECT_TRUE(parser_.Flush()); -} - -// TODO(rkuroiwa): WebVttSampleConverter doesn't handle comments yet. Once its -// implemented, this should verify that comment is in the sample. -// Verify that comment is parsed. -TEST_F(WebVttMediaParserTest, Comment) { - const char kExpectedComment[] = "NOTE This is a comment"; - std::vector expected_comment( - kExpectedComment, kExpectedComment + arraysize(kExpectedComment) - 1); - - EXPECT_CALL(init_callback_, Call(_)); - - const char kWebVtt[] = - "WEBVTT\n" - "\n" - "NOTE This is a comment\n"; - - InitializeParser(); - EXPECT_TRUE(parser_.Parse(reinterpret_cast(kWebVtt), - arraysize(kWebVtt) - 1)); - EXPECT_TRUE(parser_.Flush()); -} - -// Verify that comment with --> is rejected. -TEST_F(WebVttMediaParserTest, BadComment) { - EXPECT_CALL(init_callback_, Call(_)); - - const char kWebVtt[] = - "WEBVTT\n" - "\n" - "NOTE BAD Comment -->.\n"; - - InitializeParser(); - EXPECT_FALSE(parser_.Parse(reinterpret_cast(kWebVtt), - arraysize(kWebVtt) - 1)); - EXPECT_TRUE(parser_.Flush()); -} - -MATCHER_P(HeaderMatches, header, "") { - const std::vector& codec_config = arg.at(0)->codec_config(); - return codec_config == header; -} - -// Verify that the metadata header and the WEBVTT magic string is there. -TEST_F(WebVttMediaParserTest, Header) { - const char kHeader[] = "WEBVTT\nRegion: id=anything width=40%"; - std::vector expected_header(kHeader, - kHeader + arraysize(kHeader) - 1); - - EXPECT_CALL(init_callback_, Call(HeaderMatches(expected_header))); - ON_CALL(new_sample_callback_, Call(_, _)).WillByDefault(Return(true)); - const char kWebVtt[] = - "WEBVTT\n" - "Region: id=anything width=40%\n" - "\n" - "00:01:01.004 --> 00:04:25.092\n" - "subtitle"; - - InitializeParser(); - EXPECT_TRUE(parser_.Parse(reinterpret_cast(kWebVtt), - arraysize(kWebVtt) - 1)); - EXPECT_TRUE(parser_.Flush()); -} - -// Verify that if timing is not present after an identifier, the parser errors. -TEST_F(WebVttMediaParserTest, NoTimingAfterIdentifier) { - EXPECT_CALL(init_callback_, Call(_)); - EXPECT_CALL(new_sample_callback_, Call(_, _)).Times(0); - - const char kWebVtt[] = - "WEBVTT\n" - "\n" - "anyid\n" - "00:12.000 00:13.000\n"; // This line doesn't have -->, so error. - InitializeParser(); - EXPECT_FALSE(parser_.Parse(reinterpret_cast(kWebVtt), - arraysize(kWebVtt) - 1)); - EXPECT_TRUE(parser_.Flush()); -} - -} // namespace media -} // namespace shaka diff --git a/packager/media/formats/webvtt/webvtt_sample_converter.cc b/packager/media/formats/webvtt/webvtt_sample_converter.cc deleted file mode 100644 index 2cc5381d41..0000000000 --- a/packager/media/formats/webvtt/webvtt_sample_converter.cc +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2015 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/media/formats/webvtt/webvtt_sample_converter.h" - -#include -#include - -#include "packager/base/strings/string_util.h" -#include "packager/base/strings/stringprintf.h" -#include "packager/media/base/buffer_writer.h" -#include "packager/media/base/media_sample.h" -#include "packager/media/formats/mp4/box_buffer.h" -#include "packager/media/formats/mp4/box_definitions.h" - -namespace shaka { -namespace media { - -namespace { - -std::shared_ptr CreateEmptyCueSample(uint64_t start_time, - uint64_t end_time) { - DCHECK_GT(end_time, start_time); - mp4::VTTEmptyCueBox empty_cue_box; - - std::vector serialized; - AppendBoxToVector(&empty_cue_box, &serialized); - - std::shared_ptr empty_cue_sample = MediaSample::CopyFrom( - serialized.data(), serialized.size(), false); - empty_cue_sample->set_pts(start_time); - empty_cue_sample->set_duration(end_time - start_time); - return empty_cue_sample; -} - -void StripTrailingNewlines(const std::string& input, std::string* output) { - const size_t found = input.find_last_not_of('\n'); - if (found != std::string::npos) { - *output = input.substr(0, found + 1); - } else { - *output = input; - } -} - -mp4::VTTCueBox CueBoxFromCue(const Cue& cue) { - mp4::VTTCueBox cue_box; - if (!cue.identifier.empty()) { - cue_box.cue_id.cue_id = cue.identifier; - } - - if (!cue.settings.empty()) { - cue_box.cue_settings.settings = cue.settings; - } - - StripTrailingNewlines(cue.payload, &cue_box.cue_payload.cue_text); - return cue_box; -} - -std::string TimeToWebVttTimeStamp(uint64_t time_in_ms) { - const int milliseconds = time_in_ms % 1000; - const uint64_t seconds_left = time_in_ms / 1000; - const int seconds = seconds_left % 60; - const uint64_t minutes_left = seconds_left / 60; - const int minutes = minutes_left % 60; - const int hours = minutes_left / 60; - - return base::StringPrintf("%02d:%02d:%02d.%03d", hours, minutes, seconds, - milliseconds); -} - -std::shared_ptr CreateVTTCueBoxesSample( - const std::list& cues, - uint64_t start_time, - uint64_t end_time) { - // TODO(rkuroiwa): Source IDs must be assigned to the cues and the same cue - // should have the same ID in different samples. Probably requires a mapping - // from cues to IDs. - CHECK(!cues.empty()); - - std::vector data; - std::string cue_current_time = TimeToWebVttTimeStamp(start_time); - - BufferWriter writer; - for (const Cue* cue : cues) { - mp4::VTTCueBox cue_box = CueBoxFromCue(*cue); - // If there is internal timing, i.e. WebVTT cue timestamp, then - // cue_current_time should be populated - // "which gives the VTT timestamp associated with the start time of sample." - // TODO(rkuroiwa): Reuse TimestampToMilliseconds() to check if there is an - // internal timestamp in the payload to set CueTimeBox.cue_current_time. - cue_box.Write(&writer); - } - - std::shared_ptr sample = - MediaSample::CopyFrom(writer.Buffer(), writer.Size(), false); - sample->set_pts(start_time); - sample->set_duration(end_time - start_time); - return sample; -} - -// This function returns the minimum of cue_start_time, cue_end_time, -// current_minimum should be bigger than sweep_line. -uint64_t GetMinimumPastSweepLine(uint64_t cue_start_time, - uint64_t cue_end_time, - uint64_t sweep_line, - uint64_t current_minimum) { - DCHECK_GE(current_minimum, sweep_line); - if (cue_end_time <= sweep_line) - return current_minimum; - - // Anything below is cue_end_time > sweep_line. - if (cue_start_time > sweep_line) { - // The start time of this cue is past the sweepline, return the min. - return std::min(cue_start_time, current_minimum); - } else { - // The sweep line is at the start or in the middle of a cue. - return std::min(cue_end_time, current_minimum); - } -} - -} // namespace - -void AppendBoxToVector(mp4::Box* box, std::vector* output_vector) { - BufferWriter writer; - box->Write(&writer); - output_vector->insert(output_vector->end(), - writer.Buffer(), - writer.Buffer() + writer.Size()); -} - -WebVttSampleConverter::WebVttSampleConverter() : next_cue_start_time_(0u) {} -WebVttSampleConverter::~WebVttSampleConverter() {} - -// Note that this |sample| is either a cue or a comment. It does not have any -// info on whether the next cue is overlapping or not. -void WebVttSampleConverter::PushCue(const Cue& cue) { - if (!cue.comment.empty()) { - // A comment. Put it in the buffer and skip. - mp4::VTTAdditionalTextBox comment; - StripTrailingNewlines(cue.comment, &comment.cue_additional_text); - additional_texts_.push_back(comment); - // TODO(rkuriowa): Handle comments as samples. - - return; - } - - cues_.push_back(cue); - if (cues_.size() == 1) { - // Cannot make a decision with just one sample. Cache it and wait for - // another one. - next_cue_start_time_ = cues_.front().start_time; - return; - } - - CHECK_GE(cues_.size(), 2u); - // TODO(rkuroiwa): This isn't wrong but all the cues where - // endtime < latest cue start time - // can be processed. Change the logic so that if there are cues that meet the - // condition above, create samples immediately and remove them. - // Note: This doesn't mean that all the cues can be removed, just the ones - // that meet the condition. - bool processed_cues = HandleAllCuesButLatest(); - if (!processed_cues) - return; - - // Remove all the cues except the latest one. - auto erase_last_iterator = --cues_.end(); - cues_.erase(cues_.begin(), erase_last_iterator); -} - -void WebVttSampleConverter::Flush() { - if (cues_.empty()) - return; - if (cues_.size() == 1) { - std::list temp_list; - temp_list.push_back(&cues_.front()); - CHECK_EQ(next_cue_start_time_, cues_.front().start_time); - ready_samples_.push_back(CreateVTTCueBoxesSample( - temp_list, - next_cue_start_time_, - cues_.front().start_time + cues_.front().duration)); - cues_.clear(); - return; - } - - bool processed_cue = HandleAllCues(); - CHECK(processed_cue) - << "No cues were processed but the cues should have been flushed."; - cues_.clear(); -} - -size_t WebVttSampleConverter::ReadySamplesSize() { - return ready_samples_.size(); -} - -std::shared_ptr WebVttSampleConverter::PopSample() { - CHECK(!ready_samples_.empty()); - std::shared_ptr ret = ready_samples_.front(); - ready_samples_.pop_front(); - return ret; -} - -// TODO(rkuroiwa): Some samples may be ready. Example: -// Cues: -// |--------- 1 ---------| -// |-- 2 --| -// |-- 3 --| -// -// Samples: -// |A| B | C | -// Samples A, B, and C can be created when Cue 3 is pushed. -// Change algorithm to create A,B,C samples right away. -// Note that this requires change to the caller on which cues -// to remove. -bool WebVttSampleConverter::HandleAllCuesButLatest() { - DCHECK_GE(cues_.size(), 2u); - const Cue& latest_cue = cues_.back(); - - // Don't process the cues until the latest cue doesn't overlap with all the - // previous cues. - uint64_t max_cue_end_time = 0; // Not including the latest. - auto latest_cue_it = --cues_.end(); - for (auto cue_it = cues_.begin(); cue_it != latest_cue_it; ++cue_it) { - const Cue& cue = *cue_it; - const uint64_t cue_end_time = cue.start_time + cue.duration; - if (cue_end_time > latest_cue.start_time) - return false; - - if (max_cue_end_time < cue_end_time) - max_cue_end_time = cue_end_time; - } - // Reaching here means that the latest cue does not overlap with all - // the previous cues. - - // Because sweep_stop_time is assigned to next_cue_start_time_ it is not - // set to latest_cue.start_time here; there may be a gap between - // latest_cue.start_time and previous_cue_end_time. - // The correctness of SweepCues() doesn't change whether the sweep stops - // right before the latest cue or right before the gap. - const uint64_t sweep_stop_time = max_cue_end_time; - const uint64_t sweep_line_start = cues_.front().start_time; - bool processed_cues = - SweepCues(sweep_line_start, sweep_stop_time); - next_cue_start_time_ = sweep_stop_time; - if (next_cue_start_time_ < latest_cue.start_time) { - ready_samples_.push_back(CreateEmptyCueSample(next_cue_start_time_, - latest_cue.start_time)); - next_cue_start_time_ = latest_cue.start_time; - } - return processed_cues; -} - -bool WebVttSampleConverter::HandleAllCues() { - uint64_t latest_time = 0u; - for (const Cue& cue : cues_) { - if (cue.start_time + cue.duration > latest_time) - latest_time = cue.start_time + cue.duration; - } - const uint64_t sweep_line_start = cues_.front().start_time; - const uint64_t sweep_stop_time = latest_time; - bool processed = SweepCues(sweep_line_start, sweep_stop_time); - next_cue_start_time_ = sweep_stop_time; - return processed; -} - -bool WebVttSampleConverter::SweepCues(uint64_t sweep_line, - uint64_t sweep_stop_time) { - bool processed_cues = false; - // This is a sweep line algorithm. For every iteration, it determines active - // cues and makes a sample. - // At the end of an interation |next_start_time| should be set to the minimum - // of all the start and end times of the cues that is after |sweep_line|. - // |sweep_line| is set to |next_start_time| before the next iteration. - while (sweep_line < sweep_stop_time) { - std::list cues_for_a_sample; - uint64_t next_start_time = sweep_stop_time; - - // Put all the cues that should be displayed at sweep_line, in - // cues_for_a_sample. - // next_start_time is also updated in this loop by checking all the cues. - for (const Cue& cue : cues_) { - if (cue.start_time >= sweep_stop_time) - break; - if (cue.start_time >= next_start_time) - break; - - const uint64_t cue_end_time = cue.start_time + cue.duration; - if (cue_end_time <= sweep_line) - continue; - next_start_time = GetMinimumPastSweepLine( - cue.start_time, cue_end_time, sweep_line, next_start_time); - - if (cue.start_time <= sweep_line) { - DCHECK_GT(cue_end_time, sweep_line); - cues_for_a_sample.push_back(&cue); - } - } - - DCHECK(!cues_for_a_sample.empty()) << "For now the only use case of this " - "function is to sweep non-empty " - "cues."; - if (!cues_for_a_sample.empty()) { - ready_samples_.push_back(CreateVTTCueBoxesSample( - cues_for_a_sample, sweep_line, next_start_time)); - processed_cues = true; - } - - sweep_line = next_start_time; - } - - DCHECK_EQ(sweep_line, sweep_stop_time); - return processed_cues; -} - -} // namespace media -} // namespace shaka diff --git a/packager/media/formats/webvtt/webvtt_sample_converter.h b/packager/media/formats/webvtt/webvtt_sample_converter.h deleted file mode 100644 index de2a9527f0..0000000000 --- a/packager/media/formats/webvtt/webvtt_sample_converter.h +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2015 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 - -#ifndef PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_SAMPLE_CONVERTER_H_ -#define PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_SAMPLE_CONVERTER_H_ - -#include -#include - -#include "packager/media/formats/mp4/box.h" -#include "packager/media/formats/mp4/box_definitions.h" -#include "packager/media/formats/webvtt/cue.h" -#include "packager/status.h" - -namespace shaka { -namespace media { - -/// Appends box to vector. -/// @param box is the box to be serialized. -/// @param output_vector is where the data is appended. -void AppendBoxToVector(mp4::Box* box, std::vector* output_vector); - -/// According to the spec, when cues overlap, samples must be created.\n -/// The example below has 2 WebVTT cues:\n -/// 00:01:00.000 --> 00:02:00.000\n -/// hello\n -///\n -/// 00:01:15.000 --> 00:02:15.000\n -/// how are you?\n -///\n -/// These are added (AddSample()) as 2 samples but must be split into 3 samples -/// and 4 cues ('vttc' boxes).\n -/// First sample:\n -/// start_time: 00:01:00.000\n -/// duration: 15 seconds\n -/// cue payload: hello\n -///\n -/// Second sample:\n -/// start_time: 00:01:15.000\n -/// duration: 45 seconds\n -/// cue payload: hello\n -/// cue payload: how are you?\n -///\n -/// Third sample:\n -/// start_time: 00:02:00.000\n -/// duration: 15 seconds\n -/// cue payload: how are you?\n -///\n -/// This class buffers the samples that are passed to AddSample() and creates -/// more samples as necessary. -/// Methods are virtual only for mocking, not intended for inheritance. -class WebVttSampleConverter { - public: - WebVttSampleConverter(); - virtual ~WebVttSampleConverter(); - - /// Add a webvtt cue. - /// @param cue is a webvtt cue. - virtual void PushCue(const Cue& cue); - - /// Process all the buffered samples. - /// This finalizes the object and further calls to PushSample() may result in - /// an undefined behavior. - virtual void Flush(); - - /// @return The number of samples that are processed and ready to be popped. - virtual size_t ReadySamplesSize(); - - /// Returns a MediaSample that is non-overlapping with the previous samples - /// that it has output. The data in the sample is one or more ISO-BMFF boxes - /// for the duration of the sample. - /// @return The first sample that is ready to be processed. - virtual std::shared_ptr PopSample(); - - private: - // Handle |cues_| except the last item, and create samples from them. - // All cues that overlap with the latest cue are not processed. - // Usually the last cue (and cues that overlap with it) should not be - // processed right away because the following cues may overlap with the latest - // cue or the existing cues. - // If a cue has been proceessed, then this returns true. - bool HandleAllCuesButLatest(); - - // Same as HandleAllCuesButLatest() but it also includes the latest cue. - // If a cue has been processed, then this returns true. - bool HandleAllCues(); - - // Sweep line algorithm that handles the cues in |cues_|. - // This does not erase |cues_|. - // If a cue has been processed, this returns true. - // |sweep_line| is the start time and |sweep_stop_time| is when the sweep - // should stop. - bool SweepCues(uint64_t sweep_line, uint64_t sweep_stop_time); - - // This is going to be in 'mdat' box. Keep this around until a sample is - // ready. - std::list cues_; - - // For comment samples. - std::list additional_texts_; - - // Samples that are ready to be processed. - std::list> ready_samples_; - - // This keeps track of the max end time of the processed cues which is the - // start time of the next cue. Used to check if cue_current_time has to be set - // or an empty cue (gap) has to be added. - uint64_t next_cue_start_time_; - - DISALLOW_COPY_AND_ASSIGN(WebVttSampleConverter); -}; - -} // namespace media -} // namespace shaka - -#endif // PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_SAMPLE_CONVERTER_H_ diff --git a/packager/media/formats/webvtt/webvtt_sample_converter_unittest.cc b/packager/media/formats/webvtt/webvtt_sample_converter_unittest.cc deleted file mode 100644 index e9cbe744c0..0000000000 --- a/packager/media/formats/webvtt/webvtt_sample_converter_unittest.cc +++ /dev/null @@ -1,422 +0,0 @@ -#include "packager/media/formats/webvtt/webvtt_sample_converter.h" - -#include -#include - -#include "packager/base/strings/string_number_conversions.h" -#include "packager/media/base/media_sample.h" -#include "packager/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: - WebVttSampleConverter webvtt_sample_converter_; -}; - -// 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) { - Cue cue1; - cue1.payload = kCueMessage1; - cue1.start_time = 0; - cue1.duration = 2000; - webvtt_sample_converter_.PushCue(cue1); - - Cue cue2; - cue2.payload = kCueMessage2; - cue2.start_time = 2000; - cue2.duration = 1000; - - webvtt_sample_converter_.PushCue(cue2); - webvtt_sample_converter_.Flush(); - EXPECT_EQ(2u, webvtt_sample_converter_.ReadySamplesSize()); - - VTTCueBox first_cue_data; - first_cue_data.cue_payload.cue_text = kCueMessage1; - std::vector expected; - AppendBoxToVector(&first_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.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_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(2000, 3000, expected)); -} - -// Verify that if is a gap, then a sample is created for the gap. -TEST_F(WebVttFragmenterTest, Gap) { - Cue cue1; - cue1.payload = kCueMessage1; - cue1.start_time = 0; - cue1.duration = 1000; - webvtt_sample_converter_.PushCue(cue1); - - Cue cue2; - cue2.payload = kCueMessage2; - cue2.start_time = 2000; - cue2.duration = 1000; - webvtt_sample_converter_.PushCue(cue2); - - EXPECT_EQ(2u, webvtt_sample_converter_.ReadySamplesSize()); - - webvtt_sample_converter_.Flush(); - EXPECT_EQ(3u, webvtt_sample_converter_.ReadySamplesSize()); - - VTTCueBox first_cue_data; - first_cue_data.cue_payload.cue_text = kCueMessage1; - std::vector expected; - AppendBoxToVector(&first_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(0, 1000, expected)); - - VTTEmptyCueBox empty_cue; - expected.clear(); - AppendBoxToVector(&empty_cue, &expected); - EXPECT_THAT(webvtt_sample_converter_.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_sample_converter_.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) { - Cue cue1; - cue1.payload = kCueMessage1; - cue1.start_time = 0; - cue1.duration = 2000; - webvtt_sample_converter_.PushCue(cue1); - - Cue cue2; - cue2.payload = kCueMessage2; - cue2.start_time = 1000; - cue2.duration = 2000; - webvtt_sample_converter_.PushCue(cue2); - - Cue cue3; - cue3.payload = kCueMessage3; - cue3.start_time = 1500; - cue3.duration = 4000; - webvtt_sample_converter_.PushCue(cue3); - - webvtt_sample_converter_.Flush(); - // There should be 5 samples for [0,1000], [1000,1500], [1500,2000], - // [2000,3000], and [3000, 5500]. - EXPECT_EQ(5u, webvtt_sample_converter_.ReadySamplesSize()); - - VTTCueBox first_cue_data; - first_cue_data.cue_payload.cue_text = kCueMessage1; - std::vector expected; - AppendBoxToVector(&first_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.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_sample_converter_.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_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(1500, 2000, expected)); - - expected.clear(); - AppendBoxToVector(&second_cue_data, &expected); - AppendBoxToVector(&third_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(2000, 3000, expected)); - - expected.clear(); - AppendBoxToVector(&third_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(3000, 5500, expected)); -} - -TEST_F(WebVttFragmenterTest, OverlappingLongCue) { - Cue cue1; - cue1.payload = kCueMessage1; - cue1.start_time = 0; - cue1.duration = 10000; - webvtt_sample_converter_.PushCue(cue1); - - Cue cue2; - cue2.payload = kCueMessage2; - cue2.start_time = 1000; - cue2.duration = 5000; - webvtt_sample_converter_.PushCue(cue2); - - Cue cue3; - cue3.payload = kCueMessage3; - cue3.start_time = 2000; - cue3.duration = 1000; - webvtt_sample_converter_.PushCue(cue3); - - Cue cue4; - cue4.payload = kCueMessage4; - cue4.start_time = 8000; - cue4.duration = 1000; - webvtt_sample_converter_.PushCue(cue4); - webvtt_sample_converter_.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_sample_converter_.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_sample_converter_.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_sample_converter_.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_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(2000, 3000, expected)); - - expected.clear(); - AppendBoxToVector(&first_long_cue_data, &expected); - AppendBoxToVector(&second_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(3000, 6000, expected)); - - expected.clear(); - AppendBoxToVector(&first_long_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.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_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(8000, 9000, expected)); - - expected.clear(); - AppendBoxToVector(&first_long_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(9000, 10000, expected)); -} - -TEST_F(WebVttFragmenterTest, GapAtBeginning) { - Cue cue; - cue.payload = kCueMessage1; - cue.start_time = 1200; - cue.duration = 2000; - webvtt_sample_converter_.PushCue(cue); - - webvtt_sample_converter_.Flush(); - EXPECT_EQ(1u, webvtt_sample_converter_.ReadySamplesSize()); - - VTTCueBox cue_data; - cue_data.cue_payload.cue_text = kCueMessage1; - std::vector expected; - AppendBoxToVector(&cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(1200, 3200, expected)); -} - -TEST_F(WebVttFragmenterTest, SameStartTime) { - Cue cue1; - cue1.payload = kCueMessage1; - cue1.start_time = 0; - cue1.duration = 2000; - webvtt_sample_converter_.PushCue(cue1); - - Cue cue2; - cue2.payload = kCueMessage2; - cue2.start_time = 0; - cue2.duration = 1500; - webvtt_sample_converter_.PushCue(cue2); - - webvtt_sample_converter_.Flush(); - EXPECT_EQ(2u, webvtt_sample_converter_.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_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(0, 1500, expected)); - - expected.clear(); - AppendBoxToVector(&first_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(1500, 2000, expected)); -} - -// This test is a combination of the test cases above. -TEST_F(WebVttFragmenterTest, MoreCases) { - Cue cue1; - cue1.payload = kCueMessage1; - cue1.start_time = 0; - cue1.duration = 2000; - webvtt_sample_converter_.PushCue(cue1); - - Cue cue2; - cue2.payload = kCueMessage2; - cue2.start_time = 100; - cue2.duration = 100; - webvtt_sample_converter_.PushCue(cue2); - - Cue cue3; - cue3.payload = kCueMessage3; - cue3.start_time = 1500; - cue3.duration = 1000; - webvtt_sample_converter_.PushCue(cue3); - - Cue cue4; - cue4.payload = kCueMessage4; - cue4.start_time = 1500; - cue4.duration = 800; - webvtt_sample_converter_.PushCue(cue4); - - webvtt_sample_converter_.Flush(); - EXPECT_EQ(6u, webvtt_sample_converter_.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_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(0, 100, expected)); - - expected.clear(); - AppendBoxToVector(&first_cue_data, &expected); - AppendBoxToVector(&second_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(100, 200, expected)); - - expected.clear(); - AppendBoxToVector(&first_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.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_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(1500, 2000, expected)); - - expected.clear(); - AppendBoxToVector(&third_cue_data, &expected); - AppendBoxToVector(&fourth_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(2000, 2300, expected)); - - expected.clear(); - AppendBoxToVector(&third_cue_data, &expected); - EXPECT_THAT(webvtt_sample_converter_.PopSample(), - MatchesStartTimeEndTimeAndData(2300, 2500, expected)); -} - -} // namespace shaka -} // namespace media -} // namespace edash_packager