Removed Old WebVtt Code
Now that we have the new webvtt code in place, we don't need to keep the old solution. Change-Id: I20540ba3adf93f535f0ed011acb8e2555653522a
This commit is contained in:
parent
74c53239dd
commit
033fa65105
|
@ -20,7 +20,6 @@
|
||||||
#include "packager/media/formats/mp2t/mp2t_media_parser.h"
|
#include "packager/media/formats/mp2t/mp2t_media_parser.h"
|
||||||
#include "packager/media/formats/mp4/mp4_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/webm/webm_media_parser.h"
|
||||||
#include "packager/media/formats/webvtt/webvtt_media_parser.h"
|
|
||||||
#include "packager/media/formats/wvm/wvm_media_parser.h"
|
#include "packager/media/formats/wvm/wvm_media_parser.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -200,8 +199,6 @@ Status Demuxer::InitializeParser() {
|
||||||
case CONTAINER_WEBM:
|
case CONTAINER_WEBM:
|
||||||
parser_.reset(new WebMMediaParser());
|
parser_.reset(new WebMMediaParser());
|
||||||
break;
|
break;
|
||||||
case CONTAINER_WEBVTT:
|
|
||||||
parser_.reset(new WebVttMediaParser());
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
NOTIMPLEMENTED();
|
NOTIMPLEMENTED();
|
||||||
|
|
|
@ -17,14 +17,10 @@
|
||||||
'cue.h',
|
'cue.h',
|
||||||
'text_readers.cc',
|
'text_readers.cc',
|
||||||
'text_readers.h',
|
'text_readers.h',
|
||||||
'webvtt_media_parser.cc',
|
|
||||||
'webvtt_media_parser.h',
|
|
||||||
'webvtt_output_handler.cc',
|
'webvtt_output_handler.cc',
|
||||||
'webvtt_output_handler.h',
|
'webvtt_output_handler.h',
|
||||||
'webvtt_parser.cc',
|
'webvtt_parser.cc',
|
||||||
'webvtt_parser.h',
|
'webvtt_parser.h',
|
||||||
'webvtt_sample_converter.cc',
|
|
||||||
'webvtt_sample_converter.h',
|
|
||||||
'webvtt_segmenter.cc',
|
'webvtt_segmenter.cc',
|
||||||
'webvtt_segmenter.h',
|
'webvtt_segmenter.h',
|
||||||
'webvtt_timestamp.cc',
|
'webvtt_timestamp.cc',
|
||||||
|
@ -44,10 +40,8 @@
|
||||||
'type': '<(gtest_target_type)',
|
'type': '<(gtest_target_type)',
|
||||||
'sources': [
|
'sources': [
|
||||||
'text_readers_unittest.cc',
|
'text_readers_unittest.cc',
|
||||||
'webvtt_media_parser_unittest.cc',
|
|
||||||
'webvtt_output_handler_unittest.cc',
|
'webvtt_output_handler_unittest.cc',
|
||||||
'webvtt_parser_unittest.cc',
|
'webvtt_parser_unittest.cc',
|
||||||
'webvtt_sample_converter_unittest.cc',
|
|
||||||
'webvtt_segmenter_unittest.cc',
|
'webvtt_segmenter_unittest.cc',
|
||||||
'webvtt_pipeline_unittest.cc',
|
'webvtt_pipeline_unittest.cc',
|
||||||
'webvtt_timestamp_unittest.cc',
|
'webvtt_timestamp_unittest.cc',
|
||||||
|
|
|
@ -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 <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#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<std::string> 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<std::shared_ptr<StreamInfo>> 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<WebVttSampleConverter> 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
|
|
|
@ -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 <stdint.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#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<WebVttSampleConverter> 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<std::string> 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<WebVttSampleConverter> sample_converter_;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(WebVttMediaParser);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace media
|
|
||||||
} // namespace shaka
|
|
||||||
|
|
||||||
#endif // PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_MEDIA_PARSER_H_
|
|
|
@ -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 <gmock/gmock.h>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#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<void(
|
|
||||||
const std::vector<std::shared_ptr<StreamInfo>>& stream_info)>
|
|
||||||
MockInitCallback;
|
|
||||||
typedef testing::MockFunction<
|
|
||||||
bool(uint32_t track_id, const std::shared_ptr<MediaSample>& 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<uint8_t> 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<const uint8_t*>(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<const uint8_t*>(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<uint8_t> 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<const uint8_t*>(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<const uint8_t*>(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<const uint8_t*>(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<const uint8_t*>(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<const uint8_t*>(kWebVtt), 8));
|
|
||||||
|
|
||||||
EXPECT_CALL(new_sample_callback_, Call(_, _)).WillOnce(Return(true));
|
|
||||||
EXPECT_TRUE(parser_.Parse(reinterpret_cast<const uint8_t*>(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<const uint8_t*>(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<uint8_t> 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<const uint8_t*>(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<const uint8_t*>(kWebVtt),
|
|
||||||
arraysize(kWebVtt) - 1));
|
|
||||||
EXPECT_TRUE(parser_.Flush());
|
|
||||||
}
|
|
||||||
|
|
||||||
MATCHER_P(HeaderMatches, header, "") {
|
|
||||||
const std::vector<uint8_t>& 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<uint8_t> 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<const uint8_t*>(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<const uint8_t*>(kWebVtt),
|
|
||||||
arraysize(kWebVtt) - 1));
|
|
||||||
EXPECT_TRUE(parser_.Flush());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace media
|
|
||||||
} // namespace shaka
|
|
|
@ -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 <algorithm>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#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<MediaSample> CreateEmptyCueSample(uint64_t start_time,
|
|
||||||
uint64_t end_time) {
|
|
||||||
DCHECK_GT(end_time, start_time);
|
|
||||||
mp4::VTTEmptyCueBox empty_cue_box;
|
|
||||||
|
|
||||||
std::vector<uint8_t> serialized;
|
|
||||||
AppendBoxToVector(&empty_cue_box, &serialized);
|
|
||||||
|
|
||||||
std::shared_ptr<MediaSample> 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<MediaSample> CreateVTTCueBoxesSample(
|
|
||||||
const std::list<const Cue*>& 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<uint8_t> 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<MediaSample> 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<uint8_t>* 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<const Cue*> 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<MediaSample> WebVttSampleConverter::PopSample() {
|
|
||||||
CHECK(!ready_samples_.empty());
|
|
||||||
std::shared_ptr<MediaSample> 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<const Cue*> 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
|
|
|
@ -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 <stdint.h>
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
#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<uint8_t>* 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<MediaSample> 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<Cue> cues_;
|
|
||||||
|
|
||||||
// For comment samples.
|
|
||||||
std::list<mp4::VTTAdditionalTextBox> additional_texts_;
|
|
||||||
|
|
||||||
// Samples that are ready to be processed.
|
|
||||||
std::list<std::shared_ptr<MediaSample>> 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_
|
|
|
@ -1,422 +0,0 @@
|
||||||
#include "packager/media/formats/webvtt/webvtt_sample_converter.h"
|
|
||||||
|
|
||||||
#include <gmock/gmock.h>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#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<uint8_t> serialized;
|
|
||||||
AppendBoxToVector(&cue_box, &serialized);
|
|
||||||
std::vector<uint8_t> 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
|
|
||||||
// <start> |--- 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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
|
|
Loading…
Reference in New Issue