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:
Aaron Vaage 2018-02-05 11:16:00 -08:00
parent f35410deb1
commit c7eb2b4a7e
4 changed files with 76 additions and 55 deletions

View File

@ -26,6 +26,7 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
const char* kOutput1 = "memory://output/template-1.vtt";
const char* kOutput2 = "memory://output/template-2.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* kInput =
@ -53,8 +54,14 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
"Thank you.\n";
// Segment One
// 00:00:10.000 to 00:00:20.000
// 00:00:00.000 to 00:00:10.000
const char* kExpectedOutput1 =
"WEBVTT\n"
"\n";
// Segment Two
// 00:00:10.000 to 00:00:20.000
const char* kExpectedOutput2 =
"WEBVTT\n"
"\n"
"1\n"
@ -62,9 +69,9 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
"This blade has a dark past.\n"
"\n";
// Segment Two
// Segment Three
// 00:00:20.000 to 00:00:30.000
const char* kExpectedOutput2 =
const char* kExpectedOutput3 =
"WEBVTT\n"
"\n"
"1\n"
@ -80,9 +87,9 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
"You're a fool for traveling alone,\nso completely unprepared.\n"
"\n";
// Segment Three
// Segment Four
// 00:00:30.000 to 00:00:40.000
const char* kExpectedOutput3 =
const char* kExpectedOutput4 =
"WEBVTT\n"
"\n"
"3\n"
@ -128,6 +135,7 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) {
ASSERT_FILE_STREQ(kOutput1, kExpectedOutput1);
ASSERT_FILE_STREQ(kOutput2, kExpectedOutput2);
ASSERT_FILE_STREQ(kOutput3, kExpectedOutput3);
ASSERT_FILE_STREQ(kOutput4, kExpectedOutput4);
}
} // namespace media
} // namespace shaka

View File

@ -10,7 +10,7 @@ namespace shaka {
namespace media {
namespace {
const size_t kStreamIndex = 0;
}
} // namespace
WebVttSegmenter::WebVttSegmenter(uint64_t 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) {
while (segment_map_.size()) {
uint64_t segment = segment_map_.begin()->first;
// At this point we know that there is a single series of consecutive
// 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()) {
return status;
}
}
segment_map_.clear();
return FlushAllDownstreams();
}
@ -66,58 +68,58 @@ Status WebVttSegmenter::OnTextSample(std::shared_ptr<const TextSample> sample) {
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.
for (uint64_t segment = start_segment; segment <= ending_segment; segment++) {
segment_map_[segment].push_back(sample);
}
// Output all the segments that come before the start of this cue's first
// segment.
while (segment_map_.size()) {
// Since the segments are in accending order, we can break out of the loop
// once we catch-up to the new samples starting segment.
const uint64_t segment = segment_map_.begin()->first;
if (segment >= start_segment) {
break;
// Move forward segment-by-segment so that we output empty segments to fill
// any segments with no cues.
for (uint64_t segment = head_segment_; segment < start_segment; segment++) {
auto it = segment_map_.find(segment);
Status status;
if (it == segment_map_.end()) {
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.
Status status = OnSegmentEnd(segment);
// If we fail to output a single sample, just stop.
if (!status.ok()) {
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;
}
Status WebVttSegmenter::OnSegmentEnd(uint64_t segment) {
Status WebVttSegmenter::DispatchSegmentWithSamples(
uint64_t segment,
const WebVttSegmentSamples& samples) {
Status status;
const auto found = segment_map_.find(segment);
if (found != segment_map_.end()) {
for (const auto& sample : found->second) {
status.Update(DispatchTextSample(kStreamIndex, sample));
}
// Stop storing the segment.
segment_map_.erase(found);
for (const auto& sample : samples) {
status.Update(DispatchTextSample(kStreamIndex, sample));
}
// Only send the segment info if all the samples were accepted.
if (status.ok()) {
std::shared_ptr<SegmentInfo> info = std::make_shared<SegmentInfo>();
info->start_timestamp = segment * segment_duration_ms_;
info->duration = segment_duration_ms_;
status.Update(DispatchSegmentInfo(kStreamIndex, std::move(info)));
// Only send the segment info if all the samples were successful.
if (!status.ok()) {
return status;
}
return status;
std::shared_ptr<SegmentInfo> info = std::make_shared<SegmentInfo>();
info->start_timestamp = segment * segment_duration_ms_;
info->duration = segment_duration_ms_;
return DispatchSegmentInfo(kStreamIndex, std::move(info));
}
} // namespace media
} // namespace shaka

View File

@ -30,19 +30,20 @@ class WebVttSegmenter : public MediaHandler {
WebVttSegmenter(const WebVttSegmenter&) = delete;
WebVttSegmenter& operator=(const WebVttSegmenter&) = delete;
using WebVttSample = std::shared_ptr<const TextSample>;
using WebVttSegmentSamples = std::vector<WebVttSample>;
Status InitializeInternal() override;
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_;
using WebVttSample = std::shared_ptr<const TextSample>;
using WebVttSegment = std::vector<WebVttSample>;
// 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;
};

View File

@ -142,9 +142,13 @@ TEST_F(WebVttSegmenterTest, CreatesSegmentsForCues) {
// | |
// | | [---B---]
// | |
TEST_F(WebVttSegmenterTest, SkipsEmptySegments) {
TEST_F(WebVttSegmenterTest, OutputsEmptySegments) {
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;
@ -157,19 +161,25 @@ TEST_F(WebVttSegmenterTest, SkipsEmptySegments) {
kNoSettings, kPayload[0])));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kStartTimeSigned,
kSegmentDuration, !kSubSegment, !kEncrypted)));
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration,
!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),
OnProcess(IsTextSample(
kId[1], kStartTime + 2 * kSegmentDuration,
kStartTime + 2 * kSegmentDuration + kSampleDuration,
kNoSettings, kPayload[1])));
EXPECT_CALL(*Output(kOutputIndex),
OnProcess(IsSegmentInfo(
kStreamIndex, kStartTimeSigned + 2 * kSegmentDuration,
kSegmentDuration, !kSubSegment, !kEncrypted)));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment3Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
}