Shaka Packager SDK
Loading...
Searching...
No Matches
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
18namespace shaka {
19namespace media {
20namespace {
21const size_t kStreamIndex = 0;
22
23bool 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
34ChunkingHandler::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
40Status 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
48Status 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
66Status ChunkingHandler::OnFlushRequest(size_t /*input_stream_index*/) {
67 RETURN_IF_ERROR(EndSegmentIfStarted());
68 return FlushDownstream(kStreamIndex);
69}
70
71Status 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
80Status 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
93Status 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
167Status 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
184Status 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.