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