Handle CueEvent in TextChunker

Updated TextChunker to handle incoming CueEvents. Connecting the
text chunker with the cue alignment handler will happen in a later
CL.

Issue: #362

Change-Id: Ib1fa9f457cf4ec0ce413dadcfa7eed5895ecd628
This commit is contained in:
Aaron Vaage 2018-03-22 18:33:01 -07:00
parent daac6869fb
commit a11cbf93a7
3 changed files with 587 additions and 318 deletions

View File

@ -6,26 +6,42 @@
#include "packager/media/chunking/text_chunker.h"
#include "packager/status_macros.h"
namespace shaka {
namespace media {
namespace {
const size_t kStreamIndex = 0;
std::shared_ptr<const SegmentInfo> MakeSegmentInfo(int64_t start_ms,
int64_t end_ms) {
DCHECK_LT(start_ms, end_ms);
std::shared_ptr<SegmentInfo> info = std::make_shared<SegmentInfo>();
info->start_timestamp = start_ms;
info->duration = end_ms - start_ms;
return info;
}
} // namespace
TextChunker::TextChunker(uint64_t segment_duration_ms)
: segment_duration_ms_(segment_duration_ms) {}
TextChunker::TextChunker(int64_t segment_duration_ms)
: segment_duration_ms_(segment_duration_ms),
segment_start_ms_(0),
segment_expected_end_ms_(segment_duration_ms) {}
Status TextChunker::InitializeInternal() {
return Status::OK;
}
Status TextChunker::Process(std::unique_ptr<StreamData> stream_data) {
switch (stream_data->stream_data_type) {
Status TextChunker::Process(std::unique_ptr<StreamData> data) {
switch (data->stream_data_type) {
case StreamDataType::kStreamInfo:
return DispatchStreamInfo(kStreamIndex,
std::move(stream_data->stream_info));
return OnStreamInfo(std::move(data->stream_info));
case StreamDataType::kTextSample:
return OnTextSample(stream_data->text_sample);
return OnTextSample(data->text_sample);
case StreamDataType::kCueEvent:
return OnCueEvent(data->cue_event);
default:
return Status(error::INTERNAL_ERROR,
"Invalid stream data type for this handler");
@ -33,92 +49,75 @@ Status TextChunker::Process(std::unique_ptr<StreamData> stream_data) {
}
Status TextChunker::OnFlushRequest(size_t input_stream_index) {
// 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);
if (!status.ok()) {
return status;
}
// Keep outputting segments until all the samples leave the system.
while (segment_samples_.size()) {
RETURN_IF_ERROR(EndSegment(segment_expected_end_ms_));
}
segment_map_.clear();
return FlushAllDownstreams();
}
Status TextChunker::OnTextSample(std::shared_ptr<const TextSample> sample) {
const uint64_t start_segment = sample->start_time() / segment_duration_ms_;
Status TextChunker::OnStreamInfo(std::shared_ptr<const StreamInfo> info) {
// There is no information we need from the stream info, so just pass it
// downstream.
return DispatchStreamInfo(kStreamIndex, std::move(info));
}
// Find the last segment that overlaps the sample. Adjust the sample by one
// ms (smallest time unit) in case |EndTime| falls on the segment boundary.
DCHECK_GT(sample->duration(), 0u);
const uint64_t ending_segment =
(sample->EndTime() - 1) / segment_duration_ms_;
Status TextChunker::OnCueEvent(std::shared_ptr<const CueEvent> event) {
// We are going to cut the current segment into two using the event's time as
// the division.
const int64_t cue_time_in_ms = event->time_in_seconds * 1000;
DCHECK_GE(ending_segment, start_segment);
// Samples must always be advancing. If a sample comes in out of order,
// skip the sample.
if (head_segment_ > start_segment) {
LOG(WARNING) << "New sample has arrived out of order. Skipping sample "
<< "as segment start is " << start_segment << " and segment "
<< "head is " << head_segment_ << ".";
return Status::OK;
// In the case that there is a gap with no samples between the last sample
// and the cue event, output all the segments until we get to the segment that
// the cue event interrupts.
while (segment_expected_end_ms_ < cue_time_in_ms) {
RETURN_IF_ERROR(EndSegment(segment_expected_end_ms_));
}
// Add the sample to each segment it spans.
for (uint64_t segment = start_segment; segment <= ending_segment; segment++) {
segment_map_[segment].push_back(sample);
}
// 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 SegmentSamples 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);
}
// 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_IF_ERROR(EndSegment(cue_time_in_ms));
RETURN_IF_ERROR(DispatchCueEvent(kStreamIndex, std::move(event)));
return Status::OK;
}
Status TextChunker::DispatchSegmentWithSamples(uint64_t segment,
const SegmentSamples& samples) {
Status status;
for (const auto& sample : samples) {
status.Update(DispatchTextSample(kStreamIndex, sample));
Status TextChunker::OnTextSample(std::shared_ptr<const TextSample> sample) {
// Output all segments that come before our new sample.
while (segment_expected_end_ms_ <= sample->start_time()) {
RETURN_IF_ERROR(EndSegment(segment_expected_end_ms_));
}
// Only send the segment info if all the samples were successful.
if (!status.ok()) {
return status;
}
segment_samples_.push_back(std::move(sample));
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));
return Status::OK;
}
Status TextChunker::EndSegment(int64_t segment_actual_end_ms) {
// Output all the samples that are part of the segment.
for (const auto& sample : segment_samples_) {
RETURN_IF_ERROR(DispatchTextSample(kStreamIndex, sample));
}
RETURN_IF_ERROR(DispatchSegmentInfo(
kStreamIndex, MakeSegmentInfo(segment_start_ms_, segment_actual_end_ms)));
// Create a new segment that comes right after the old segment and remove all
// samples that don't cross over into the new segment.
StartNewSegment(segment_actual_end_ms);
return Status::OK;
}
void TextChunker::StartNewSegment(int64_t start_ms) {
segment_start_ms_ = start_ms;
segment_expected_end_ms_ = start_ms + segment_duration_ms_;
// Remove all samples that no longer overlap with the new segment.
segment_samples_.remove_if(
[start_ms](const std::shared_ptr<const TextSample>& sample) {
return sample->EndTime() <= start_ms;
});
}
} // namespace media
} // namespace shaka

View File

@ -7,43 +7,41 @@
#ifndef PACKAGER_MEDIA_CHUNKING_TEXT_CHUNKER_H_
#define PACKAGER_MEDIA_CHUNKING_TEXT_CHUNKER_H_
#include <stdint.h>
#include <map>
#include <vector>
#include <list>
#include "packager/media/base/media_handler.h"
#include "packager/status.h"
namespace shaka {
namespace media {
class TextChunker : public MediaHandler {
public:
explicit TextChunker(uint64_t segment_duration_ms);
protected:
Status Process(std::unique_ptr<StreamData> stream_data) override;
Status OnFlushRequest(size_t input_stream_index) override;
explicit TextChunker(int64_t segment_duration_ms);
private:
TextChunker(const TextChunker&) = delete;
TextChunker& operator=(const TextChunker&) = delete;
using SegmentSamples = std::vector<std::shared_ptr<const TextSample>>;
Status InitializeInternal() override;
Status Process(std::unique_ptr<StreamData> stream_data) override;
Status OnFlushRequest(size_t input_stream_index) override;
Status OnStreamInfo(std::shared_ptr<const StreamInfo> info);
Status OnCueEvent(std::shared_ptr<const CueEvent> cue);
Status OnTextSample(std::shared_ptr<const TextSample> sample);
Status DispatchSegmentWithSamples(uint64_t segment,
const SegmentSamples& samples);
Status EndSegment(int64_t segment_actual_end_ms);
void StartNewSegment(int64_t start_ms);
uint64_t segment_duration_ms_;
int64_t segment_duration_ms_;
// Mapping of segment number to segment.
std::map<uint64_t, SegmentSamples> segment_map_;
uint64_t head_segment_ = 0;
// The segment that we are currently outputting samples for. The segment
// will end once a new sample with start time greater or equal to the
// segment's end time arrives.
int64_t segment_start_ms_;
int64_t segment_expected_end_ms_;
std::list<std::shared_ptr<const TextSample>> segment_samples_;
};
} // namespace media

View File

@ -15,298 +15,570 @@ namespace shaka {
namespace media {
namespace {
const int64_t kStartTime = 0;
const int64_t kSegmentDuration = 10000; // 10 seconds
const uint64_t kStreamIndex = 0;
const size_t kStreamIndex = 0;
const size_t kInputs = 1;
const size_t kOutputs = 1;
const size_t kInputCount = 1;
const size_t kOutputCount = 1;
const size_t kInputIndex = 0;
const size_t kOutputIndex = 0;
const size_t kInput = 0;
const size_t kOutput = 0;
const bool kEncrypted = true;
const bool kSubSegment = true;
const char* kId[] = {"cue 1 id", "cue 2 id"};
const char* kPayload[] = {"cue 1 payload", "cue 2 payload"};
const int64_t kTick = 50;
const std::string kNoSettings = "";
const char* kNoId = "";
const char* kNoSettings = "";
const char* kNoPayload = "";
} // namespace
class TextChunkerTest : public MediaHandlerTestBase {
protected:
void SetUp() {
ASSERT_OK(
SetUpAndInitializeGraph(std::make_shared<TextChunker>(kSegmentDuration),
kInputCount, kOutputCount));
void Init(int64_t segment_duration) {
ASSERT_OK(SetUpAndInitializeGraph(
std::make_shared<TextChunker>(segment_duration), kInputs, kOutputs));
}
};
// When a cue ends on a segment boundry, it does not create a cue with a 0 ms
// duration
// S0 S1
// | |
// |[---A---]|
// | |
TEST_F(TextChunkerTest, CueEndingOnSegmentStart) {
const int64_t kSampleDuration = kSegmentDuration;
TEST_F(TextChunkerTest, SampleEndingOnSegmentStart) {
const int64_t kSegmentStart = 0;
const int64_t kSegmentDuration = kTick;
const int64_t kSampleStart = 0;
const int64_t kSampleDuration = kTick;
const int64_t kSampleEnd = kSampleStart + kSampleDuration;
Init(kSegmentDuration);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutputIndex), OnProcess(IsStreamInfo(kStreamIndex)));
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment One
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSampleStart, kSampleEnd,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTextSample(kId[0], kStartTime, kStartTime + kSampleDuration,
kNoSettings, kPayload[0])));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kStartTime, kSegmentDuration,
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegmentStart, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
EXPECT_CALL(*Output(kOutput), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(kStreamIndex,
GetTextStreamInfo())));
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kId[0], kStartTime,
kStartTime + kSampleDuration, kPayload[0]))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromStreamInfo(kStreamIndex, GetTextStreamInfo())));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSampleStart, kSampleEnd, kNoPayload))));
ASSERT_OK(Input(kInput)->FlushAllDownstreams());
}
// Each cue belongs in its own segment, so before each cue is passed
// downstream, a 'input of segment' message should be passed downstream.
// |
// [---A---] |
// | [---B---]
// |
TEST_F(TextChunkerTest, CreatesSegmentsForCues) {
// Divide segment duration by 2 so that the sample duration won't be a full
// segment.
const int64_t kSampleDuration = kSegmentDuration / 2;
// S0 S1 S2
// | | |
// |[-A-] | |
// | |[-B-] |
// | | |
TEST_F(TextChunkerTest, CreatesSegmentsForSamples) {
const int64_t kSegmentDuration = 2 * kTick;
const int64_t kSegment0Start = 0;
const int64_t kSegment1Start = kSegment0Start + kSegmentDuration;
const int64_t kSample0Start = 0;
const int64_t kSample0Duration = kTick;
const int64_t kSample0End = kSample0Start + kSample0Duration;
const int64_t kSample1Start = 3 * kTick;
const int64_t kSample1Duration = kTick;
const int64_t kSample1End = kSample1Start + kSample1Duration;
Init(kSegmentDuration);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutputIndex), OnProcess(IsStreamInfo(kStreamIndex)));
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment One
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTextSample(kId[0], kStartTime, kStartTime + kSampleDuration,
kNoSettings, kPayload[0])));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kStartTime, kSegmentDuration,
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment0Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Two
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample1Start, kSample1End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTextSample(kId[1], kStartTime + kSegmentDuration,
kStartTime + kSegmentDuration + kSampleDuration,
kNoSettings, kPayload[1])));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kStartTime + kSegmentDuration,
kSegmentDuration, !kSubSegment, !kEncrypted)));
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
EXPECT_CALL(*Output(kOutput), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(kStreamIndex,
GetTextStreamInfo())));
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kId[0], kStartTime,
kStartTime + kSampleDuration, kPayload[0]))));
ASSERT_OK(
Input(kInputIndex)
->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kId[1], kStartTime + kSegmentDuration,
kStartTime + kSegmentDuration + kSampleDuration,
kPayload[1]))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromStreamInfo(kStreamIndex, GetTextStreamInfo())));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSample0Start, kSample0End, kNoPayload))));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSample1Start, kSample1End, kNoPayload))));
ASSERT_OK(Input(kInput)->FlushAllDownstreams());
}
// [---A---] | |
// | |
// | | [---B---]
// | |
// S0 S1 S2 S3
// | | | |
// |[-A-] | | |
// | | |[-B-] |
// | | | |
TEST_F(TextChunkerTest, OutputsEmptySegments) {
const int64_t kSampleDuration = kSegmentDuration / 2;
const int64_t kSegment1Start = kStartTime;
const int64_t kSegmentDuration = 2 * kTick;
const int64_t kSegment0Start = 0;
const int64_t kSegment1Start = kSegment0Start + kSegmentDuration;
const int64_t kSegment2Start = kSegment1Start + kSegmentDuration;
const int64_t kSegment3Start = kSegment2Start + kSegmentDuration;
const int64_t kSample0Start = 0;
const int64_t kSample0Duration = kTick;
const int64_t kSample0End = kSample0Start + kSample0Duration;
const int64_t kSample1Start = 4 * kTick;
const int64_t kSample1Duration = kTick;
const int64_t kSample1End = kSample1Start + kSample1Duration;
Init(kSegmentDuration);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutputIndex), OnProcess(IsStreamInfo(kStreamIndex)));
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment One
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTextSample(kId[0], kStartTime, kStartTime + kSampleDuration,
kNoSettings, kPayload[0])));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration,
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment0Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Two (empty segment)
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment2Start, kSegmentDuration,
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, 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(kOutput),
OnProcess(IsTextSample(kNoId, kSample1Start, kSample1End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment3Start, kSegmentDuration,
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment2Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(kStreamIndex,
GetTextStreamInfo())));
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kId[0], kStartTime,
kStartTime + kSampleDuration, kPayload[0]))));
ASSERT_OK(
Input(kInputIndex)
->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kId[1], kStartTime + 2 * kSegmentDuration,
kStartTime + 2 * kSegmentDuration + kSampleDuration,
kPayload[1]))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
}
// When a cue crossing the segment boundary, the cue should be included in
// both segments.
// |
// [-----A-----|---------]
// |
TEST_F(TextChunkerTest, CueCrossesSegments) {
const int64_t kSampleDuration = 2 * kSegmentDuration;
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutputIndex), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment One
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTextSample(kId[0], kStartTime, kStartTime + kSampleDuration,
kNoSettings, kPayload[0])));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kStartTime, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Two
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTextSample(kId[0], kStartTime, kStartTime + kSampleDuration,
kNoSettings, kPayload[0])));
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kStartTime + kSegmentDuration,
kSegmentDuration, !kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(kStreamIndex,
GetTextStreamInfo())));
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kId[0], kStartTime,
kStartTime + kSampleDuration, kPayload[0]))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
}
class TextChunkerOrderTest : public MediaHandlerTestBase {};
TEST_F(TextChunkerOrderTest, PreservesOrder) {
const size_t kInputs = 1;
const size_t kOutputs = 1;
const size_t kInput = 0;
const size_t kOutput = 0;
const int64_t kDuration = 10000;
const int64_t kSegmentStart1 = 0;
const int64_t kSegmentStart2 = kDuration;
ASSERT_OK(SetUpAndInitializeGraph(std::make_shared<TextChunker>(kDuration),
kInputs, kOutputs));
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kInput)));
// Segment One
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample("1", 5000u, 8500u, "",
"WebVtt testing Line 1 (5.0 - 8.5)")));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample("2", 5000u, 8500u, "",
"WebVtt testing Line 2 (5.0 - 8.5)")));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample("3", 5000u, 12500u, "",
"WebVtt testing (5.0 - 12.5)")));
EXPECT_CALL(*Output(kOutput), OnProcess(IsSegmentInfo(
kInput, kSegmentStart1, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Two
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample("3", 5000u, 12500u, "",
"WebVtt testing (5.0 - 12.5)")));
EXPECT_CALL(*Output(kOutput), OnProcess(IsSegmentInfo(
kInput, kSegmentStart2, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutput), OnFlush(kInput));
EXPECT_CALL(*Output(kOutput), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromStreamInfo(0, GetTextStreamInfo())));
StreamData::FromStreamInfo(kStreamIndex, GetTextStreamInfo())));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kOutput,
GetTextSample("1", 5000, 8500, "WebVtt testing Line 1 (5.0 - 8.5)"))));
kStreamIndex,
GetTextSample(kNoId, kSample0Start, kSample0End, kNoPayload))));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kOutput,
GetTextSample("2", 5000, 8500, "WebVtt testing Line 2 (5.0 - 8.5)"))));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kOutput,
GetTextSample("3", 5000, 12500, "WebVtt testing (5.0 - 12.5)"))));
kStreamIndex,
GetTextSample(kNoId, kSample1Start, kSample1End, kNoPayload))));
ASSERT_OK(Input(kInput)->FlushAllDownstreams());
}
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
// S0 S1 S2
// | | |
// | [---A---] |
// | | |
TEST_F(TextChunkerTest, SampleCrossesSegments) {
const int64_t kSegmentDuration = 2 * kTick;
const int64_t kSegment0Start = 0;
const int64_t kSegment1Start = kSegment0Start + kSegmentDuration;
const int64_t kSampleStart = kTick;
const int64_t kSampleDuration = 2 * kTick;
const int64_t kSampleEnd = kSampleStart + kSampleDuration;
Init(kSegmentDuration);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment One
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSampleStart, kSampleEnd,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment0Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Two
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSampleStart, kSampleEnd,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutput), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromStreamInfo(kStreamIndex, GetTextStreamInfo())));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSampleStart, kSampleEnd, kNoPayload))));
ASSERT_OK(Input(kInput)->FlushAllDownstreams());
}
// S0 S1 S2 S3
// | | | |
// | [-A----|----] | |
// | [-B----|----] | |
// | [-C----|----------|----] |
// | | | |
TEST_F(TextChunkerTest, PreservesOrder) {
const int64_t kSegmentDuration = 2 * kTick;
const int64_t kSegment0Start = 0;
const int64_t kSegment1Start = kSegment0Start + kSegmentDuration;
const int64_t kSegment2Start = kSegment1Start + kSegmentDuration;
const int64_t kSample0Start = kTick;
const int64_t kSample0Duration = 2 * kTick;
const int64_t kSample0End = kSample0Start + kSample0Duration;
const int64_t kSample1Start = kTick;
const int64_t kSample1Duration = 2 * kTick;
const int64_t kSample1End = kSample1Start + kSample1Duration;
const int64_t kSample2Start = kTick;
const int64_t kSample2Duration = 4 * kTick;
const int64_t kSample2End = kSample2Start + kSample2Duration;
const char* kSample0Id = "sample 0";
const char* kSample1Id = "sample 1";
const char* kSample2Id = "sample 2";
Init(kSegmentDuration);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment One
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kSample0Id, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kSample1Id, kSample1Start, kSample1End,
kNoSettings, kNoPayload)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kSample2Id, kSample2Start, kSample2End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment0Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Two
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kSample0Id, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kSample1Id, kSample1Start, kSample1End,
kNoSettings, kNoPayload)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kSample2Id, kSample2Start, kSample2End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Two
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kSample2Id, kSample2Start, kSample2End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment2Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutput), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromStreamInfo(kStreamIndex, GetTextStreamInfo())));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kSample0Id, kSample0Start, kSample0End, kNoPayload))));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kSample1Id, kSample1Start, kSample1End, kNoPayload))));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kSample2Id, kSample2Start, kSample2End, kNoPayload))));
ASSERT_OK(Input(kInput)->FlushAllDownstreams());
}
// S0 S1 S2 S3 S4 S5
// | | | | | |
// | [--|-----|--A--|-----|--] |
// | | [--|--B--|--] | |
// | | | | | |
TEST_F(TextChunkerTest, NestedSamples) {
const int64_t kSegmentDuration = 2 * kTick;
const int64_t kSample0Start = 1 * kTick;
const int64_t kSample0Duration = 8 * kTick;
const int64_t kSample0End = kSample0Start + kSample0Duration;
const int64_t kSample1Start = 3 * kTick;
const int64_t kSample1Duration = 4 * kTick;
const int64_t kSample1End = kSample1Start + kSample1Duration;
const int64_t kSegment0Start = 0;
const int64_t kSegment1Start = kSegment0Start + kSegmentDuration;
const int64_t kSegment2Start = kSegment1Start + kSegmentDuration;
const int64_t kSegment3Start = kSegment2Start + kSegmentDuration;
const int64_t kSegment4Start = kSegment3Start + kSegmentDuration;
Init(kSegmentDuration);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment 0
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment0Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment 1
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample1Start, kSample1End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment 2
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample1Start, kSample1End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment2Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment 3
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample1Start, kSample1End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment3Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment 4
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment4Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutput), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromStreamInfo(kStreamIndex, GetTextStreamInfo())));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSample0Start, kSample0End, kNoPayload))));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSample1Start, kSample1End, kNoPayload))));
ASSERT_OK(Input(kInput)->FlushAllDownstreams());
}
// S0 S1 S2 S3
// | | | |
// | [------A--------]| |
// | | |[--B--] |
// | | | |
TEST_F(TextChunkerTest, SecondSampleStartsAfterMultiSegmentSampleEnds) {
const int64_t kSegmentDuration = 2 * kTick;
const int64_t kSegment0Start = 0;
const int64_t kSegment1Start = kSegment0Start + kSegmentDuration;
const int64_t kSegment2Start = kSegment1Start + kSegmentDuration;
const int64_t kSample0Start = kTick;
const int64_t kSample0Duration = 3 * kTick;
const int64_t kSample0End = kSample0Start + kSample0Duration;
const int64_t kSample1Start = 4 * kTick;
const int64_t kSample1Duration = kTick;
const int64_t kSample1End = kSample1Start + kSample1Duration;
Init(kSegmentDuration);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment One
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment0Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Two
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample0Start, kSample0End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
// Segment Three
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSample1Start, kSample1End,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment2Start, kSegmentDuration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutput), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromStreamInfo(kStreamIndex, GetTextStreamInfo())));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSample0Start, kSample0End, kNoPayload))));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSample1Start, kSample1End, kNoPayload))));
ASSERT_OK(Input(kInput)->FlushAllDownstreams());
}
// S0 C0 C1 S1 * *
// s0 s1 s2 * * s3
// | | | |
// | [---|-----A----|---] |
// | | | |
// The segment duration is 8 ticks, but with the cues being injected, c0 will
// become s1, c1 will become s3, and S1 will become s3.
TEST_F(TextChunkerTest, SampleSpanningMultipleCues) {
const int64_t kSegmentDuration = 8 * kTick;
const int64_t kSampleStart = kTick;
const int64_t kSampleDuration = 4 * kTick;
const int64_t kSampleEnd = kSampleStart + kSampleDuration;
const int64_t kCue0Time = 2 * kTick;
const double kCue0TimeInSeconds = kCue0Time / 1000.0;
const int64_t kCue1Time = 4 * kTick;
const double kCue1TimeInSeconds = kCue1Time / 1000.0;
const int64_t kSegment0Start = 0;
const int64_t kSegment0Duration = 2 * kTick;
const int64_t kSegment1Start = kSegment0Start + kSegment0Duration;
const int64_t kSegment1Duration = 2 * kTick;
const int64_t kSegment2Start = kSegment1Start + kSegment1Duration;
const int64_t kSegment2Duration = 8 * kTick;
Init(kSegmentDuration);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutput), OnProcess(IsStreamInfo(kStreamIndex)));
// Segment 0 and Cue 0
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSampleStart, kSampleEnd,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment0Start, kSegment0Duration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsCueEvent(kStreamIndex, kCue0TimeInSeconds)));
// Segment 1 and Cue 1
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSampleStart, kSampleEnd,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegment1Duration,
!kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutput),
OnProcess(IsCueEvent(kStreamIndex, kCue1TimeInSeconds)));
// Segment 2
EXPECT_CALL(*Output(kOutput),
OnProcess(IsTextSample(kNoId, kSampleStart, kSampleEnd,
kNoSettings, kNoPayload)));
EXPECT_CALL(
*Output(kOutput),
OnProcess(IsSegmentInfo(kStreamIndex, kSegment2Start, kSegment2Duration,
!kSubSegment, !kEncrypted)));
}
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromStreamInfo(kStreamIndex, GetTextStreamInfo())));
ASSERT_OK(Input(kInput)->Dispatch(StreamData::FromTextSample(
kStreamIndex,
GetTextSample(kNoId, kSampleStart, kSampleEnd, kNoPayload))));
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromCueEvent(kStreamIndex, GetCueEvent(kCue0TimeInSeconds))));
ASSERT_OK(Input(kInput)->Dispatch(
StreamData::FromCueEvent(kStreamIndex, GetCueEvent(kCue1TimeInSeconds))));
ASSERT_OK(Input(kInput)->FlushAllDownstreams());
}
} // namespace media