Shaka Packager SDK
Loading...
Searching...
No Matches
text_chunker.cc
1// Copyright 2017 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/media/chunking/text_chunker.h>
8
9#include <absl/log/check.h>
10
11#include <packager/macros/status.h>
12
13namespace shaka {
14namespace media {
15namespace {
16const size_t kStreamIndex = 0;
17} // namespace
18
19TextChunker::TextChunker(double segment_duration_in_seconds,
20 int64_t start_segment_number)
21 : segment_duration_in_seconds_(segment_duration_in_seconds),
22 segment_number_(start_segment_number){};
23
24Status TextChunker::Process(std::unique_ptr<StreamData> data) {
25 switch (data->stream_data_type) {
26 case StreamDataType::kStreamInfo:
27 return OnStreamInfo(std::move(data->stream_info));
28 case StreamDataType::kTextSample:
29 return OnTextSample(data->text_sample);
30 case StreamDataType::kCueEvent:
31 return OnCueEvent(data->cue_event);
32 default:
33 return Status(error::INTERNAL_ERROR,
34 "Invalid stream data type for this handler");
35 }
36}
37
38Status TextChunker::OnFlushRequest(size_t /*input_stream_index*/) {
39 // Keep outputting segments until all the samples leave the system. Calling
40 // |DispatchSegment| will remove samples over time.
41 while (samples_in_current_segment_.size()) {
42 RETURN_IF_ERROR(DispatchSegment(segment_duration_));
43 }
44
45 return FlushAllDownstreams();
46}
47
48Status TextChunker::OnStreamInfo(std::shared_ptr<const StreamInfo> info) {
49 time_scale_ = info->time_scale();
50 segment_duration_ = ScaleTime(segment_duration_in_seconds_);
51
52 return DispatchStreamInfo(kStreamIndex, std::move(info));
53}
54
55Status TextChunker::OnCueEvent(std::shared_ptr<const CueEvent> event) {
56 // We are going to end the current segment prematurely using the cue event's
57 // time as the new segment end.
58
59 // Because the cue should have been inserted into the stream such that no
60 // later sample could start before it does, we know that there should
61 // be no later samples starting before the cue event.
62
63 // Convert the event's time to be scaled to the time of each sample.
64 const int64_t event_time = ScaleTime(event->time_in_seconds);
65 // Output all full segments before the segment that the cue event interupts.
66 while (segment_start_ + segment_duration_ < event_time) {
67 RETURN_IF_ERROR(DispatchSegment(segment_duration_));
68 }
69
70 const int64_t shorten_duration = event_time - segment_start_;
71 RETURN_IF_ERROR(DispatchSegment(shorten_duration));
72 return DispatchCueEvent(kStreamIndex, std::move(event));
73}
74
75Status TextChunker::OnTextSample(std::shared_ptr<const TextSample> sample) {
76 // Output all segments that come before our new sample.
77 const int64_t sample_start = sample->start_time();
78
79 // If we have not seen a sample yet, base all segments off the first sample's
80 // start time.
81 if (segment_start_ < 0) {
82 // Force the first segment to start at the segment that would have started
83 // before the sample. This should allow segments from different streams to
84 // align.
85 segment_start_ = (sample_start / segment_duration_) * segment_duration_;
86 }
87
88 // We need to write all the segments that would have ended before the new
89 // sample started.
90 while (sample_start >= segment_start_ + segment_duration_) {
91 // |DispatchSegment| will advance |segment_start_|.
92 RETURN_IF_ERROR(DispatchSegment(segment_duration_));
93 }
94
95 samples_in_current_segment_.push_back(std::move(sample));
96
97 return Status::OK;
98}
99
100Status TextChunker::DispatchSegment(int64_t duration) {
101 DCHECK_GT(duration, 0) << "Segment duration should always be positive";
102
103 // Output all the samples that are part of the segment.
104 for (const auto& sample : samples_in_current_segment_) {
105 RETURN_IF_ERROR(DispatchTextSample(kStreamIndex, sample));
106 }
107
108 // Output the segment info.
109 std::shared_ptr<SegmentInfo> info = std::make_shared<SegmentInfo>();
110 info->start_timestamp = segment_start_;
111 info->duration = duration;
112 info->segment_number = segment_number_++;
113
114 RETURN_IF_ERROR(DispatchSegmentInfo(kStreamIndex, std::move(info)));
115
116 // Move onto the next segment.
117 const int64_t new_segment_start = segment_start_ + duration;
118 segment_start_ = new_segment_start;
119
120 // Remove all samples that end before the (new) current segment started.
121 samples_in_current_segment_.remove_if(
122 [new_segment_start](const std::shared_ptr<const TextSample>& sample) {
123 // For the sample to even be in this list, it should have started
124 // before the (new) current segment.
125 DCHECK_LT(sample->start_time(), new_segment_start);
126 return sample->EndTime() <= new_segment_start;
127 });
128
129 return Status::OK;
130}
131
132int64_t TextChunker::ScaleTime(double seconds) const {
133 DCHECK_GT(time_scale_, 0) << "Need positive time scale to scale time.";
134 return static_cast<int64_t>(seconds * time_scale_);
135}
136} // namespace media
137} // namespace shaka
All the methods that are virtual are virtual for mocking.