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