Shaka Packager SDK
Loading...
Searching...
No Matches
segmenter.cc
1// Copyright 2014 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/formats/mp4/segmenter.h>
8
9#include <cstddef>
10#include <cstdint>
11#include <memory>
12#include <string>
13#include <utility>
14#include <vector>
15
16#include <absl/log/check.h>
17#include <absl/log/log.h>
18
19#include <packager/media/base/buffer_writer.h>
20#include <packager/media/base/encryption_config.h>
21#include <packager/media/base/fourccs.h>
22#include <packager/media/base/id3_tag.h>
23#include <packager/media/base/media_handler.h>
24#include <packager/media/base/media_sample.h>
25#include <packager/media/base/muxer_options.h>
26#include <packager/media/base/muxer_util.h>
27#include <packager/media/base/protection_system_specific_info.h>
28#include <packager/media/base/stream_info.h>
29#include <packager/media/event/progress_listener.h>
30#include <packager/media/formats/mp4/box_definitions.h>
31#include <packager/media/formats/mp4/fragmenter.h>
32#include <packager/media/formats/mp4/key_frame_info.h>
33#include <packager/status.h>
34#include <packager/version/version.h>
35
36namespace shaka {
37namespace media {
38namespace mp4 {
39
40namespace {
41
42int64_t Rescale(int64_t time_in_old_scale,
43 int32_t old_scale,
44 int32_t new_scale) {
45 return static_cast<double>(time_in_old_scale) / old_scale * new_scale;
46}
47
48} // namespace
49
50Segmenter::Segmenter(const MuxerOptions& options,
51 std::unique_ptr<FileType> ftyp,
52 std::unique_ptr<Movie> moov)
53 : options_(options),
54 ftyp_(std::move(ftyp)),
55 moov_(std::move(moov)),
56 moof_(new MovieFragment()),
57 fragment_buffer_(new BufferWriter()),
58 sidx_(new SegmentIndex()) {}
59
60Segmenter::~Segmenter() {}
61
62Status Segmenter::Initialize(
63 const std::vector<std::shared_ptr<const StreamInfo>>& streams,
64 MuxerListener* muxer_listener,
65 ProgressListener* progress_listener) {
66 DCHECK_LT(0u, streams.size());
67 muxer_listener_ = muxer_listener;
68 progress_listener_ = progress_listener;
69 moof_->header.sequence_number = 0;
70
71 moof_->tracks.resize(streams.size());
72 fragmenters_.resize(streams.size());
73 stream_durations_.resize(streams.size());
74
75 for (uint32_t i = 0; i < streams.size(); ++i) {
76 moof_->tracks[i].header.track_id = i + 1;
77 if (streams[i]->stream_type() == kStreamVideo) {
78 // Use the first video stream as the reference stream (which is 1-based).
79 if (sidx_->reference_id == 0)
80 sidx_->reference_id = i + 1;
81 }
82
83 const EditList& edit_list = moov_->tracks[i].edit.list;
84 int64_t edit_list_offset = 0;
85 if (edit_list.edits.size() > 0) {
86 DCHECK_EQ(edit_list.edits.size(), 1u);
87 edit_list_offset = edit_list.edits.front().media_time;
88 }
89
90 fragmenters_[i].reset(
91 new Fragmenter(streams[i], &moof_->tracks[i], edit_list_offset));
92 }
93
94 // Choose the first stream if there is no VIDEO.
95 if (sidx_->reference_id == 0)
96 sidx_->reference_id = 1;
97 sidx_->timescale = streams[GetReferenceStreamId()]->time_scale();
98
99 // Use media duration as progress target.
100 progress_target_ = streams[GetReferenceStreamId()]->duration();
101
102 // Use the reference stream's time scale as movie time scale.
103 moov_->header.timescale = sidx_->timescale;
104 moof_->header.sequence_number = 1;
105
106 // Fill in version information.
107 const std::string version = GetPackagerVersion();
108 if (!version.empty()) {
109 moov_->metadata.handler.handler_type = FOURCC_ID32;
110 moov_->metadata.id3v2.language.code = "eng";
111
112 Id3Tag id3_tag;
113 id3_tag.AddPrivateFrame(GetPackagerProjectUrl(), version);
114 CHECK(id3_tag.WriteToVector(&moov_->metadata.id3v2.id3v2_data));
115 }
116 return DoInitialize();
117}
118
119Status Segmenter::Finalize() {
120 // Set movie duration. Note that the duration in mvhd, tkhd, mdhd should not
121 // be touched, i.e. kept at 0. The updated moov box will be written to output
122 // file for VOD and static live case only.
123 moov_->extends.header.fragment_duration = 0;
124 for (size_t i = 0; i < stream_durations_.size(); ++i) {
125 int64_t duration =
126 Rescale(stream_durations_[i], moov_->tracks[i].media.header.timescale,
127 moov_->header.timescale);
128 if (duration >
129 static_cast<int64_t>(moov_->extends.header.fragment_duration))
130 moov_->extends.header.fragment_duration = duration;
131 }
132 return DoFinalize();
133}
134
135Status Segmenter::AddSample(size_t stream_id, const MediaSample& sample) {
136 // Set default sample duration if it has not been set yet.
137 if (moov_->extends.tracks[stream_id].default_sample_duration == 0) {
138 moov_->extends.tracks[stream_id].default_sample_duration =
139 sample.duration();
140 }
141
142 DCHECK_LT(stream_id, fragmenters_.size());
143 Fragmenter* fragmenter = fragmenters_[stream_id].get();
144 if (fragmenter->fragment_finalized()) {
145 return Status(error::FRAGMENT_FINALIZED,
146 "Current fragment is finalized already.");
147 }
148
149 Status status = fragmenter->AddSample(sample);
150 if (!status.ok())
151 return status;
152
153 // The duration of the first sample may have been adjusted, so use
154 // the duration of the second sample instead.
155 if (num_samples_ < 2) {
156 sample_durations_[num_samples_] = sample.duration();
157 num_samples_++;
158 }
159 stream_durations_[stream_id] += sample.duration();
160 return Status::OK;
161}
162
163Status Segmenter::FinalizeSegment(size_t stream_id,
164 const SegmentInfo& segment_info) {
165 if (segment_info.key_rotation_encryption_config) {
166 FinalizeFragmentForKeyRotation(
167 stream_id, segment_info.is_encrypted,
168 *segment_info.key_rotation_encryption_config);
169 }
170
171 DCHECK_LT(stream_id, fragmenters_.size());
172 Fragmenter* specified_fragmenter = fragmenters_[stream_id].get();
173 DCHECK(specified_fragmenter);
174 Status status = specified_fragmenter->FinalizeFragment();
175 if (!status.ok())
176 return status;
177
178 // Check if all tracks are ready for fragmentation.
179 for (const std::unique_ptr<Fragmenter>& fragmenter : fragmenters_) {
180 if (!fragmenter->fragment_finalized())
181 return Status::OK;
182 }
183
184 MediaData mdat;
185 // Data offset relative to 'moof': moof size + mdat header size.
186 // The code will also update box sizes for moof_ and its child boxes.
187 uint64_t data_offset = moof_->ComputeSize() + mdat.HeaderSize();
188 // 'traf' should follow 'mfhd' moof header box.
189 uint64_t next_traf_position = moof_->HeaderSize() + moof_->header.box_size();
190 for (size_t i = 0; i < moof_->tracks.size(); ++i) {
191 TrackFragment& traf = moof_->tracks[i];
192 if (traf.auxiliary_offset.offsets.size() > 0) {
193 DCHECK_EQ(traf.auxiliary_offset.offsets.size(), 1u);
194 DCHECK(!traf.sample_encryption.sample_encryption_entries.empty());
195
196 next_traf_position += traf.box_size();
197 // SampleEncryption 'senc' box should be the last box in 'traf'.
198 // |auxiliary_offset| should point to the data of SampleEncryption.
199 traf.auxiliary_offset.offsets[0] =
200 next_traf_position - traf.sample_encryption.box_size() +
201 traf.sample_encryption.HeaderSize() +
202 sizeof(uint32_t); // for sample count field in 'senc'
203 }
204 traf.runs[0].data_offset = data_offset + mdat.data_size;
205 mdat.data_size += static_cast<uint32_t>(fragmenters_[i]->data()->Size());
206 }
207
208 // Generate segment reference.
209 sidx_->references.resize(sidx_->references.size() + 1);
210 fragmenters_[GetReferenceStreamId()]->GenerateSegmentReference(
211 &sidx_->references[sidx_->references.size() - 1]);
212 sidx_->references[sidx_->references.size() - 1].referenced_size =
213 data_offset + mdat.data_size;
214
215 const uint64_t moof_start_offset = fragment_buffer_->Size();
216
217 // Write the fragment to buffer.
218 moof_->Write(fragment_buffer_.get());
219 mdat.WriteHeader(fragment_buffer_.get());
220
221 bool first_key_frame = true;
222 for (const std::unique_ptr<Fragmenter>& fragmenter : fragmenters_) {
223 // https://goo.gl/xcFus6 6. Trick play requirements
224 // 6.10. If using fMP4, I-frame segments must include the 'moof' header
225 // associated with the I-frame. It also implies that only the first key
226 // frame can be included.
227 if (!fragmenter->key_frame_infos().empty() && first_key_frame) {
228 const KeyFrameInfo& key_frame_info =
229 fragmenter->key_frame_infos().front();
230 first_key_frame = false;
231 key_frame_infos_.push_back(
232 {key_frame_info.timestamp, moof_start_offset,
233 fragment_buffer_->Size() - moof_start_offset + key_frame_info.size});
234 }
235 fragment_buffer_->AppendBuffer(*fragmenter->data());
236 }
237
238 // Increase sequence_number for next fragment.
239 ++moof_->header.sequence_number;
240
241 for (std::unique_ptr<Fragmenter>& fragmenter : fragmenters_)
242 fragmenter->ClearFragmentFinalized();
243
244 if (segment_info.is_chunk) {
245 // Finalize the completed chunk for the LL-DASH case.
246 status = DoFinalizeChunk(segment_info.segment_number);
247 if (!status.ok())
248 return status;
249 }
250
251 if (!segment_info.is_subsegment || segment_info.is_final_chunk_in_seg) {
252 // Finalize the segment.
253 status = DoFinalizeSegment(segment_info.segment_number);
254
255 // Reset segment information to initial state.
256 sidx_->references.clear();
257 key_frame_infos_.clear();
258 return status;
259 }
260 return Status::OK;
261}
262
263int32_t Segmenter::GetReferenceTimeScale() const {
264 return moov_->header.timescale;
265}
266
267double Segmenter::GetDuration() const {
268 int64_t duration = moov_->extends.header.fragment_duration;
269 if (duration == 0) {
270 // Handling the case where this is not properly initialized.
271 return 0.0;
272 }
273 return static_cast<double>(duration) / moov_->header.timescale;
274}
275
276void Segmenter::UpdateProgress(uint64_t progress) {
277 accumulated_progress_ += progress;
278
279 if (!progress_listener_)
280 return;
281 if (progress_target_ == 0)
282 return;
283 // It might happen that accumulated progress exceeds progress_target due to
284 // computation errors, e.g. rounding error. Cap it so it never reports > 100%
285 // progress.
286 if (accumulated_progress_ >= progress_target_) {
287 progress_listener_->OnProgress(1.0);
288 } else {
289 progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
290 progress_target_);
291 }
292}
293
294void Segmenter::SetComplete() {
295 if (!progress_listener_)
296 return;
297 progress_listener_->OnProgress(1.0);
298}
299
300uint32_t Segmenter::GetReferenceStreamId() {
301 DCHECK(sidx_);
302 return sidx_->reference_id - 1;
303}
304
305void Segmenter::FinalizeFragmentForKeyRotation(
306 size_t stream_id,
307 bool fragment_encrypted,
308 const EncryptionConfig& encryption_config) {
309 if (options_.mp4_params.include_pssh_in_stream) {
310 moof_->pssh.clear();
311 const auto& key_system_info = encryption_config.key_system_info;
312 for (const ProtectionSystemSpecificInfo& system : key_system_info) {
313 if (system.psshs.empty())
314 continue;
315 ProtectionSystemSpecificHeader pssh;
316 pssh.raw_box = system.psshs;
317 moof_->pssh.push_back(pssh);
318 }
319 } else {
320 LOG(WARNING)
321 << "Key rotation and no pssh in stream may not work well together.";
322 }
323
324 // Skip the following steps if the current fragment is not going to be
325 // encrypted. 'pssh' box needs to be included in the fragment, which is
326 // performed above, regardless of whether the fragment is encrypted. This is
327 // necessary for two reasons: 1) Requesting keys before reaching encrypted
328 // content avoids playback delay due to license requests; 2) In Chrome, CDM
329 // must be initialized before starting the playback and CDM can only be
330 // initialized with a valid 'pssh'.
331 if (!fragment_encrypted)
332 return;
333
334 DCHECK_LT(stream_id, moof_->tracks.size());
335 TrackFragment& traf = moof_->tracks[stream_id];
336 traf.sample_group_descriptions.resize(traf.sample_group_descriptions.size() +
337 1);
338 SampleGroupDescription& sample_group_description =
339 traf.sample_group_descriptions.back();
340 sample_group_description.grouping_type = FOURCC_seig;
341
342 sample_group_description.cenc_sample_encryption_info_entries.resize(1);
343 CencSampleEncryptionInfoEntry& sample_group_entry =
344 sample_group_description.cenc_sample_encryption_info_entries.back();
345 sample_group_entry.is_protected = 1;
346 sample_group_entry.per_sample_iv_size = encryption_config.per_sample_iv_size;
347 sample_group_entry.constant_iv = encryption_config.constant_iv;
348 sample_group_entry.crypt_byte_block = encryption_config.crypt_byte_block;
349 sample_group_entry.skip_byte_block = encryption_config.skip_byte_block;
350 sample_group_entry.key_id = encryption_config.key_id;
351}
352
353} // namespace mp4
354} // namespace media
355} // namespace shaka
virtual void AddPrivateFrame(const std::string &owner, const std::string &data)
Definition id3_tag.cc:54
virtual bool WriteToVector(std::vector< uint8_t > *output)
Definition id3_tag.cc:72
Class to hold a media sample.
This class listens to progress updates events.
Status AddSample(const MediaSample &sample)
Definition fragmenter.cc:73
Status FinalizeFragment()
Finalize and optimize the fragment.
All the methods that are virtual are virtual for mocking.
virtual uint32_t HeaderSize() const
Definition box.cc:61
void WriteHeader(BufferWriter *writer)
Definition box.cc:44
uint32_t ComputeSize()
Definition box.cc:56
uint32_t box_size()
Definition box.h:56
uint32_t HeaderSize() const final
Definition box.cc:81
Tracks key frame information.