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