Shaka Packager SDK
chunking_handler.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/chunking_handler.h>
8 
9 #include <algorithm>
10 
11 #include <absl/log/check.h>
12 #include <absl/log/log.h>
13 
14 #include <packager/macros/logging.h>
15 #include <packager/macros/status.h>
16 #include <packager/media/base/media_sample.h>
17 
18 namespace shaka {
19 namespace media {
20 namespace {
21 const size_t kStreamIndex = 0;
22 
23 bool IsNewSegmentIndex(int64_t new_index, int64_t current_index) {
24  return new_index != current_index &&
25  // Index is calculated from pts, which could decrease. We do not expect
26  // it to decrease by more than one segment though, which could happen
27  // only if there is a big overlap in the timeline, in which case, we
28  // will create a new segment and leave it to the player to handle it.
29  new_index != current_index - 1;
30 }
31 
32 } // namespace
33 
34 ChunkingHandler::ChunkingHandler(const ChunkingParams& chunking_params)
35  : chunking_params_(chunking_params) {
36  CHECK_NE(chunking_params.segment_duration_in_seconds, 0u);
37  segment_number_ = chunking_params.start_segment_number;
38 }
39 
40 Status ChunkingHandler::InitializeInternal() {
41  if (num_input_streams() != 1 || next_output_stream_index() != 1) {
42  return Status(error::INVALID_ARGUMENT,
43  "Expects exactly one input and one output.");
44  }
45  return Status::OK;
46 }
47 
48 Status ChunkingHandler::Process(std::unique_ptr<StreamData> stream_data) {
49  switch (stream_data->stream_data_type) {
50  case StreamDataType::kStreamInfo:
51  return OnStreamInfo(std::move(stream_data->stream_info));
52  case StreamDataType::kCueEvent:
53  return OnCueEvent(std::move(stream_data->cue_event));
54  case StreamDataType::kSegmentInfo:
55  VLOG(3) << "Droppping existing segment info.";
56  return Status::OK;
57  case StreamDataType::kMediaSample:
58  return OnMediaSample(std::move(stream_data->media_sample));
59  default:
60  VLOG(3) << "Stream data type "
61  << static_cast<int>(stream_data->stream_data_type) << " ignored.";
62  return Dispatch(std::move(stream_data));
63  }
64 }
65 
66 Status ChunkingHandler::OnFlushRequest(size_t /*input_stream_index*/) {
67  RETURN_IF_ERROR(EndSegmentIfStarted());
68  return FlushDownstream(kStreamIndex);
69 }
70 
71 Status ChunkingHandler::OnStreamInfo(std::shared_ptr<const StreamInfo> info) {
72  time_scale_ = info->time_scale();
73  segment_duration_ =
74  chunking_params_.segment_duration_in_seconds * time_scale_;
75  subsegment_duration_ =
76  chunking_params_.subsegment_duration_in_seconds * time_scale_;
77  return DispatchStreamInfo(kStreamIndex, std::move(info));
78 }
79 
80 Status ChunkingHandler::OnCueEvent(std::shared_ptr<const CueEvent> event) {
81  RETURN_IF_ERROR(EndSegmentIfStarted());
82  const double event_time_in_seconds = event->time_in_seconds;
83  RETURN_IF_ERROR(DispatchCueEvent(kStreamIndex, std::move(event)));
84 
85  // Force start new segment after cue event.
86  segment_start_time_ = std::nullopt;
87  // |cue_offset_| will be applied to sample timestamp so the segment after cue
88  // point have duration ~= |segment_duration_|.
89  cue_offset_ = event_time_in_seconds * time_scale_;
90  return Status::OK;
91 }
92 
93 Status ChunkingHandler::OnMediaSample(
94  std::shared_ptr<const MediaSample> sample) {
95  DCHECK_GT(time_scale_, 0) << "kStreamInfo should arrive before kMediaSample";
96 
97  const int64_t timestamp = sample->pts();
98 
99  bool started_new_segment = false;
100  const bool can_start_new_segment =
101  sample->is_key_frame() || !chunking_params_.segment_sap_aligned;
102  if (can_start_new_segment) {
103  const int64_t segment_index =
104  timestamp < cue_offset_ ? 0
105  : (timestamp - cue_offset_) / segment_duration_;
106  if (!segment_start_time_ ||
107  IsNewSegmentIndex(segment_index, current_segment_index_)) {
108  current_segment_index_ = segment_index;
109  // Reset subsegment index.
110  current_subsegment_index_ = 0;
111 
112  RETURN_IF_ERROR(EndSegmentIfStarted());
113  segment_start_time_ = timestamp;
114  subsegment_start_time_ = timestamp;
115  max_segment_time_ = timestamp + sample->duration();
116  started_new_segment = true;
117  }
118  }
119 
120  // This handles the LL-DASH case.
121  // On each media sample, which is the basis for a chunk,
122  // we must increment the current_subsegment_index_
123  // in order to hit FinalizeSegment() within Segmenter.
124  if (!started_new_segment && chunking_params_.low_latency_dash_mode) {
125  current_subsegment_index_++;
126 
127  RETURN_IF_ERROR(EndSubsegmentIfStarted());
128  subsegment_start_time_ = timestamp;
129  }
130 
131  // Here, a subsegment refers to a fragment that is within a segment.
132  // This fragment size can be set with the 'fragment_duration' cmd arg.
133  // This is NOT for the LL-DASH case.
134  if (!started_new_segment && IsSubsegmentEnabled() &&
135  !chunking_params_.low_latency_dash_mode) {
136  const bool can_start_new_subsegment =
137  sample->is_key_frame() || !chunking_params_.subsegment_sap_aligned;
138  if (can_start_new_subsegment) {
139  const int64_t subsegment_index =
140  (timestamp - segment_start_time_.value()) / subsegment_duration_;
141  if (IsNewSegmentIndex(subsegment_index, current_subsegment_index_)) {
142  current_subsegment_index_ = subsegment_index;
143 
144  RETURN_IF_ERROR(EndSubsegmentIfStarted());
145  subsegment_start_time_ = timestamp;
146  }
147  }
148  }
149 
150  VLOG(3) << "Sample ts: " << timestamp << " "
151  << " duration: " << sample->duration() << " scale: " << time_scale_
152  << (segment_start_time_ ? " dispatch " : " discard ");
153  if (!segment_start_time_) {
154  DCHECK(!subsegment_start_time_);
155  // Discard samples before segment start. If the segment has started,
156  // |segment_start_time_| won't be null.
157  return Status::OK;
158  }
159 
160  segment_start_time_ = std::min(segment_start_time_.value(), timestamp);
161  subsegment_start_time_ = std::min(subsegment_start_time_.value(), timestamp);
162  max_segment_time_ =
163  std::max(max_segment_time_, timestamp + sample->duration());
164  return DispatchMediaSample(kStreamIndex, std::move(sample));
165 }
166 
167 Status ChunkingHandler::EndSegmentIfStarted() {
168  if (!segment_start_time_)
169  return Status::OK;
170 
171  auto segment_info = std::make_shared<SegmentInfo>();
172  segment_info->start_timestamp = segment_start_time_.value();
173  segment_info->duration = max_segment_time_ - segment_start_time_.value();
174  segment_info->segment_number = segment_number_++;
175 
176  if (chunking_params_.low_latency_dash_mode) {
177  segment_info->is_chunk = true;
178  segment_info->is_final_chunk_in_seg = true;
179  }
180 
181  return DispatchSegmentInfo(kStreamIndex, std::move(segment_info));
182 }
183 
184 Status ChunkingHandler::EndSubsegmentIfStarted() const {
185  if (!subsegment_start_time_)
186  return Status::OK;
187 
188  auto subsegment_info = std::make_shared<SegmentInfo>();
189  subsegment_info->start_timestamp = subsegment_start_time_.value();
190  subsegment_info->duration =
191  max_segment_time_ - subsegment_start_time_.value();
192  subsegment_info->is_subsegment = true;
193  if (chunking_params_.low_latency_dash_mode)
194  subsegment_info->is_chunk = true;
195  return DispatchSegmentInfo(kStreamIndex, std::move(subsegment_info));
196 }
197 
198 } // namespace media
199 } // namespace shaka
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66