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