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