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