Output Empty WebVTT Text Segments
Have the text webvtt pipeline output empty segments rather than skipping them. Bug: #324 Change-Id: I175cf22b65b241f6bf566aba2440518f3df95277
This commit is contained in:
parent
f35410deb1
commit
c7eb2b4a7e
|
@ -26,6 +26,7 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
|
||||||
const char* kOutput1 = "memory://output/template-1.vtt";
|
const char* kOutput1 = "memory://output/template-1.vtt";
|
||||||
const char* kOutput2 = "memory://output/template-2.vtt";
|
const char* kOutput2 = "memory://output/template-2.vtt";
|
||||||
const char* kOutput3 = "memory://output/template-3.vtt";
|
const char* kOutput3 = "memory://output/template-3.vtt";
|
||||||
|
const char* kOutput4 = "memory://output/template-4.vtt";
|
||||||
|
|
||||||
const char* kInputFile = "memory://input.vtt";
|
const char* kInputFile = "memory://input.vtt";
|
||||||
const char* kInput =
|
const char* kInput =
|
||||||
|
@ -53,8 +54,14 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
|
||||||
"Thank you.\n";
|
"Thank you.\n";
|
||||||
|
|
||||||
// Segment One
|
// Segment One
|
||||||
// 00:00:10.000 to 00:00:20.000
|
// 00:00:00.000 to 00:00:10.000
|
||||||
const char* kExpectedOutput1 =
|
const char* kExpectedOutput1 =
|
||||||
|
"WEBVTT\n"
|
||||||
|
"\n";
|
||||||
|
|
||||||
|
// Segment Two
|
||||||
|
// 00:00:10.000 to 00:00:20.000
|
||||||
|
const char* kExpectedOutput2 =
|
||||||
"WEBVTT\n"
|
"WEBVTT\n"
|
||||||
"\n"
|
"\n"
|
||||||
"1\n"
|
"1\n"
|
||||||
|
@ -62,9 +69,9 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
|
||||||
"This blade has a dark past.\n"
|
"This blade has a dark past.\n"
|
||||||
"\n";
|
"\n";
|
||||||
|
|
||||||
// Segment Two
|
// Segment Three
|
||||||
// 00:00:20.000 to 00:00:30.000
|
// 00:00:20.000 to 00:00:30.000
|
||||||
const char* kExpectedOutput2 =
|
const char* kExpectedOutput3 =
|
||||||
"WEBVTT\n"
|
"WEBVTT\n"
|
||||||
"\n"
|
"\n"
|
||||||
"1\n"
|
"1\n"
|
||||||
|
@ -80,9 +87,9 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
|
||||||
"You're a fool for traveling alone,\nso completely unprepared.\n"
|
"You're a fool for traveling alone,\nso completely unprepared.\n"
|
||||||
"\n";
|
"\n";
|
||||||
|
|
||||||
// Segment Three
|
// Segment Four
|
||||||
// 00:00:30.000 to 00:00:40.000
|
// 00:00:30.000 to 00:00:40.000
|
||||||
const char* kExpectedOutput3 =
|
const char* kExpectedOutput4 =
|
||||||
"WEBVTT\n"
|
"WEBVTT\n"
|
||||||
"\n"
|
"\n"
|
||||||
"3\n"
|
"3\n"
|
||||||
|
@ -128,6 +135,7 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
|
||||||
ASSERT_FILE_STREQ(kOutput1, kExpectedOutput1);
|
ASSERT_FILE_STREQ(kOutput1, kExpectedOutput1);
|
||||||
ASSERT_FILE_STREQ(kOutput2, kExpectedOutput2);
|
ASSERT_FILE_STREQ(kOutput2, kExpectedOutput2);
|
||||||
ASSERT_FILE_STREQ(kOutput3, kExpectedOutput3);
|
ASSERT_FILE_STREQ(kOutput3, kExpectedOutput3);
|
||||||
|
ASSERT_FILE_STREQ(kOutput4, kExpectedOutput4);
|
||||||
}
|
}
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace {
|
namespace {
|
||||||
const size_t kStreamIndex = 0;
|
const size_t kStreamIndex = 0;
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
WebVttSegmenter::WebVttSegmenter(uint64_t segment_duration_ms)
|
WebVttSegmenter::WebVttSegmenter(uint64_t segment_duration_ms)
|
||||||
: segment_duration_ms_(segment_duration_ms) {}
|
: segment_duration_ms_(segment_duration_ms) {}
|
||||||
|
@ -33,16 +33,18 @@ Status WebVttSegmenter::Process(std::unique_ptr<StreamData> stream_data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Status WebVttSegmenter::OnFlushRequest(size_t input_stream_index) {
|
Status WebVttSegmenter::OnFlushRequest(size_t input_stream_index) {
|
||||||
while (segment_map_.size()) {
|
// At this point we know that there is a single series of consecutive
|
||||||
uint64_t segment = segment_map_.begin()->first;
|
// segments, all we need to do is run through all of them.
|
||||||
|
for (const auto& pair : segment_map_) {
|
||||||
|
Status status = DispatchSegmentWithSamples(pair.first, pair.second);
|
||||||
|
|
||||||
// OnSegmentEnd will remove the segment from the map.
|
|
||||||
Status status = OnSegmentEnd(segment);
|
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
segment_map_.clear();
|
||||||
|
|
||||||
return FlushAllDownstreams();
|
return FlushAllDownstreams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,58 +68,58 @@ Status WebVttSegmenter::OnTextSample(std::shared_ptr<const TextSample> sample) {
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we are accepting this new segment, its starting segment is our
|
|
||||||
// new head segment.
|
|
||||||
head_segment_ = start_segment;
|
|
||||||
|
|
||||||
// Add the sample to each segment it spans.
|
// Add the sample to each segment it spans.
|
||||||
for (uint64_t segment = start_segment; segment <= ending_segment; segment++) {
|
for (uint64_t segment = start_segment; segment <= ending_segment; segment++) {
|
||||||
segment_map_[segment].push_back(sample);
|
segment_map_[segment].push_back(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output all the segments that come before the start of this cue's first
|
// Move forward segment-by-segment so that we output empty segments to fill
|
||||||
// segment.
|
// any segments with no cues.
|
||||||
while (segment_map_.size()) {
|
for (uint64_t segment = head_segment_; segment < start_segment; segment++) {
|
||||||
// Since the segments are in accending order, we can break out of the loop
|
auto it = segment_map_.find(segment);
|
||||||
// once we catch-up to the new samples starting segment.
|
|
||||||
const uint64_t segment = segment_map_.begin()->first;
|
Status status;
|
||||||
if (segment >= start_segment) {
|
if (it == segment_map_.end()) {
|
||||||
break;
|
const WebVttSegmentSamples kNoSamples;
|
||||||
|
status.Update(DispatchSegmentWithSamples(segment, kNoSamples));
|
||||||
|
} else {
|
||||||
|
// We found a segment, output all the samples. Remove it from the map as
|
||||||
|
// we should never need to write to it again.
|
||||||
|
status.Update(DispatchSegmentWithSamples(segment, it->second));
|
||||||
|
segment_map_.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output the segment. If there is an error, there is no reason to continue.
|
// If we fail to output a single sample, just stop.
|
||||||
Status status = OnSegmentEnd(segment);
|
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Jump ahead to the start of this segment as we should never have any samples
|
||||||
|
// start before |start_segment|.
|
||||||
|
head_segment_ = start_segment;
|
||||||
|
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status WebVttSegmenter::OnSegmentEnd(uint64_t segment) {
|
Status WebVttSegmenter::DispatchSegmentWithSamples(
|
||||||
|
uint64_t segment,
|
||||||
|
const WebVttSegmentSamples& samples) {
|
||||||
Status status;
|
Status status;
|
||||||
|
for (const auto& sample : samples) {
|
||||||
const auto found = segment_map_.find(segment);
|
|
||||||
if (found != segment_map_.end()) {
|
|
||||||
for (const auto& sample : found->second) {
|
|
||||||
status.Update(DispatchTextSample(kStreamIndex, sample));
|
status.Update(DispatchTextSample(kStreamIndex, sample));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop storing the segment.
|
// Only send the segment info if all the samples were successful.
|
||||||
segment_map_.erase(found);
|
if (!status.ok()) {
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only send the segment info if all the samples were accepted.
|
|
||||||
if (status.ok()) {
|
|
||||||
std::shared_ptr<SegmentInfo> info = std::make_shared<SegmentInfo>();
|
std::shared_ptr<SegmentInfo> info = std::make_shared<SegmentInfo>();
|
||||||
info->start_timestamp = segment * segment_duration_ms_;
|
info->start_timestamp = segment * segment_duration_ms_;
|
||||||
info->duration = segment_duration_ms_;
|
info->duration = segment_duration_ms_;
|
||||||
|
|
||||||
status.Update(DispatchSegmentInfo(kStreamIndex, std::move(info)));
|
return DispatchSegmentInfo(kStreamIndex, std::move(info));
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -30,19 +30,20 @@ class WebVttSegmenter : public MediaHandler {
|
||||||
WebVttSegmenter(const WebVttSegmenter&) = delete;
|
WebVttSegmenter(const WebVttSegmenter&) = delete;
|
||||||
WebVttSegmenter& operator=(const WebVttSegmenter&) = delete;
|
WebVttSegmenter& operator=(const WebVttSegmenter&) = delete;
|
||||||
|
|
||||||
|
using WebVttSample = std::shared_ptr<const TextSample>;
|
||||||
|
using WebVttSegmentSamples = std::vector<WebVttSample>;
|
||||||
|
|
||||||
Status InitializeInternal() override;
|
Status InitializeInternal() override;
|
||||||
|
|
||||||
Status OnTextSample(std::shared_ptr<const TextSample> sample);
|
Status OnTextSample(std::shared_ptr<const TextSample> sample);
|
||||||
|
|
||||||
Status OnSegmentEnd(uint64_t segment);
|
Status DispatchSegmentWithSamples(uint64_t segment,
|
||||||
|
const WebVttSegmentSamples& samples);
|
||||||
|
|
||||||
uint64_t segment_duration_ms_;
|
uint64_t segment_duration_ms_;
|
||||||
|
|
||||||
using WebVttSample = std::shared_ptr<const TextSample>;
|
|
||||||
using WebVttSegment = std::vector<WebVttSample>;
|
|
||||||
|
|
||||||
// Mapping of segment number to segment.
|
// Mapping of segment number to segment.
|
||||||
std::map<uint64_t, WebVttSegment> segment_map_;
|
std::map<uint64_t, WebVttSegmentSamples> segment_map_;
|
||||||
uint64_t head_segment_ = 0;
|
uint64_t head_segment_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -142,9 +142,13 @@ TEST_F(WebVttSegmenterTest, CreatesSegmentsForCues) {
|
||||||
// | |
|
// | |
|
||||||
// | | [---B---]
|
// | | [---B---]
|
||||||
// | |
|
// | |
|
||||||
TEST_F(WebVttSegmenterTest, SkipsEmptySegments) {
|
TEST_F(WebVttSegmenterTest, OutputsEmptySegments) {
|
||||||
const uint64_t kSampleDuration = kSegmentDuration / 2;
|
const uint64_t kSampleDuration = kSegmentDuration / 2;
|
||||||
|
|
||||||
|
const int64_t kSegment1Start = kStartTime;
|
||||||
|
const int64_t kSegment2Start = kSegment1Start + kSegmentDuration;
|
||||||
|
const int64_t kSegment3Start = kSegment2Start + kSegmentDuration;
|
||||||
|
|
||||||
{
|
{
|
||||||
testing::InSequence s;
|
testing::InSequence s;
|
||||||
|
|
||||||
|
@ -157,19 +161,25 @@ TEST_F(WebVttSegmenterTest, SkipsEmptySegments) {
|
||||||
kNoSettings, kPayload[0])));
|
kNoSettings, kPayload[0])));
|
||||||
EXPECT_CALL(
|
EXPECT_CALL(
|
||||||
*Output(kOutputIndex),
|
*Output(kOutputIndex),
|
||||||
OnProcess(IsSegmentInfo(kStreamIndex, kStartTimeSigned,
|
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration,
|
||||||
kSegmentDuration, !kSubSegment, !kEncrypted)));
|
!kSubSegment, !kEncrypted)));
|
||||||
|
|
||||||
// Segment Two
|
// Segment Two (empty segment)
|
||||||
|
EXPECT_CALL(
|
||||||
|
*Output(kOutputIndex),
|
||||||
|
OnProcess(IsSegmentInfo(kStreamIndex, kSegment2Start, kSegmentDuration,
|
||||||
|
!kSubSegment, !kEncrypted)));
|
||||||
|
|
||||||
|
// Segment Three
|
||||||
EXPECT_CALL(*Output(kOutputIndex),
|
EXPECT_CALL(*Output(kOutputIndex),
|
||||||
OnProcess(IsTextSample(
|
OnProcess(IsTextSample(
|
||||||
kId[1], kStartTime + 2 * kSegmentDuration,
|
kId[1], kStartTime + 2 * kSegmentDuration,
|
||||||
kStartTime + 2 * kSegmentDuration + kSampleDuration,
|
kStartTime + 2 * kSegmentDuration + kSampleDuration,
|
||||||
kNoSettings, kPayload[1])));
|
kNoSettings, kPayload[1])));
|
||||||
EXPECT_CALL(*Output(kOutputIndex),
|
EXPECT_CALL(
|
||||||
OnProcess(IsSegmentInfo(
|
*Output(kOutputIndex),
|
||||||
kStreamIndex, kStartTimeSigned + 2 * kSegmentDuration,
|
OnProcess(IsSegmentInfo(kStreamIndex, kSegment3Start, kSegmentDuration,
|
||||||
kSegmentDuration, !kSubSegment, !kEncrypted)));
|
!kSubSegment, !kEncrypted)));
|
||||||
|
|
||||||
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
|
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue