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* 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue