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