Shaka Packager SDK
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 
27 namespace shaka {
28 namespace media {
29 namespace mp4 {
30 
31 namespace {
32 
33 int64_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 
41 Segmenter::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 
51 Segmenter::~Segmenter() {}
52 
53 Status 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 
110 Status 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 
126 Status 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 
154 Status 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 
254 int32_t Segmenter::GetReferenceTimeScale() const {
255  return moov_->header.timescale;
256 }
257 
258 double 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 
267 void 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 
283 void Segmenter::SetComplete() {
284  if (!progress_listener_) return;
285  progress_listener_->OnProgress(1.0);
286 }
287 
288 uint32_t Segmenter::GetReferenceStreamId() {
289  DCHECK(sidx_);
290  return sidx_->reference_id - 1;
291 }
292 
293 void 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.
Definition: media_sample.h:25
This class listens to progress updates events.
Status AddSample(const MediaSample &sample)
Definition: fragmenter.cc:67
Status FinalizeFragment()
Finalize and optimize the fragment.
Definition: fragmenter.cc:161
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66
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.