Shaka Packager SDK
fragmenter.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/fragmenter.h"
8 
9 #include <algorithm>
10 #include <limits>
11 
12 #include "packager/media/base/audio_stream_info.h"
13 #include "packager/media/base/buffer_writer.h"
14 #include "packager/media/base/media_sample.h"
15 #include "packager/media/formats/mp4/box_definitions.h"
16 #include "packager/media/formats/mp4/key_frame_info.h"
17 
18 namespace shaka {
19 namespace media {
20 namespace mp4 {
21 
22 namespace {
23 const int64_t kInvalidTime = std::numeric_limits<int64_t>::max();
24 
25 uint64_t GetSeekPreroll(const StreamInfo& stream_info) {
26  if (stream_info.stream_type() != kStreamAudio)
27  return 0;
28  const AudioStreamInfo& audio_stream_info =
29  static_cast<const AudioStreamInfo&>(stream_info);
30  return audio_stream_info.seek_preroll_ns();
31 }
32 
33 void NewSampleEncryptionEntry(const DecryptConfig& decrypt_config,
34  bool use_constant_iv,
35  TrackFragment* traf) {
36  SampleEncryption& sample_encryption = traf->sample_encryption;
37  SampleEncryptionEntry sample_encryption_entry;
38  if (!use_constant_iv)
39  sample_encryption_entry.initialization_vector = decrypt_config.iv();
40  sample_encryption_entry.subsamples = decrypt_config.subsamples();
41  sample_encryption.sample_encryption_entries.push_back(
42  sample_encryption_entry);
43  traf->auxiliary_size.sample_info_sizes.push_back(
44  sample_encryption_entry.ComputeSize());
45 }
46 
47 } // namespace
48 
49 Fragmenter::Fragmenter(std::shared_ptr<const StreamInfo> stream_info,
50  TrackFragment* traf)
51  : stream_info_(std::move(stream_info)),
52  use_decoding_timestamp_in_timeline_(false),
53  traf_(traf),
54  seek_preroll_(GetSeekPreroll(*stream_info_)),
55  fragment_initialized_(false),
56  fragment_finalized_(false),
57  fragment_duration_(0),
58  earliest_presentation_time_(kInvalidTime),
59  first_sap_time_(kInvalidTime) {
60  DCHECK(stream_info_);
61  DCHECK(traf);
62 }
63 
64 Fragmenter::~Fragmenter() {}
65 
67  if (sample.duration() == 0) {
68  LOG(WARNING) << "Unexpected sample with zero duration @ dts "
69  << sample.dts();
70  }
71 
72  if (!fragment_initialized_) {
73  Status status = InitializeFragment(sample.dts());
74  if (!status.ok())
75  return status;
76  }
77 
78  if (sample.side_data_size() > 0)
79  LOG(WARNING) << "MP4 samples do not support side data. Side data ignored.";
80 
81  // Fill in sample parameters. It will be optimized later.
82  traf_->runs[0].sample_sizes.push_back(
83  static_cast<uint32_t>(sample.data_size()));
84  traf_->runs[0].sample_durations.push_back(sample.duration());
85  traf_->runs[0].sample_flags.push_back(
86  sample.is_key_frame() ? 0 : TrackFragmentHeader::kNonKeySampleMask);
87 
88  if (sample.decrypt_config()) {
89  NewSampleEncryptionEntry(
90  *sample.decrypt_config(),
91  !stream_info_->encryption_config().constant_iv.empty(), traf_);
92  }
93 
94  if (stream_info_->stream_type() == StreamType::kStreamVideo &&
95  sample.is_key_frame()) {
96  key_frame_infos_.push_back({static_cast<uint64_t>(sample.pts()),
97  data_->Size(), sample.data_size()});
98  }
99 
100  data_->AppendArray(sample.data(), sample.data_size());
101  fragment_duration_ += sample.duration();
102 
103  const int64_t pts = sample.pts();
104  const int64_t dts = sample.dts();
105 
106  const int64_t timestamp = use_decoding_timestamp_in_timeline_ ? dts : pts;
107  // Set |earliest_presentation_time_| to |timestamp| if |timestamp| is smaller
108  // or if it is not yet initialized (kInvalidTime > timestamp is always true).
109  if (earliest_presentation_time_ > timestamp)
110  earliest_presentation_time_ = timestamp;
111 
112  traf_->runs[0].sample_composition_time_offsets.push_back(pts - dts);
113  if (pts != dts)
114  traf_->runs[0].flags |= TrackFragmentRun::kSampleCompTimeOffsetsPresentMask;
115 
116  if (sample.is_key_frame()) {
117  if (first_sap_time_ == kInvalidTime)
118  first_sap_time_ = pts;
119  }
120  return Status::OK;
121 }
122 
123 Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
124  fragment_initialized_ = true;
125  fragment_finalized_ = false;
126  traf_->decode_time.decode_time = first_sample_dts;
127  traf_->runs.clear();
128  traf_->runs.resize(1);
129  traf_->runs[0].flags = TrackFragmentRun::kDataOffsetPresentMask;
130  traf_->auxiliary_size.sample_info_sizes.clear();
131  traf_->auxiliary_offset.offsets.clear();
132  traf_->sample_encryption.sample_encryption_entries.clear();
133  traf_->sample_group_descriptions.clear();
134  traf_->sample_to_groups.clear();
135  traf_->header.sample_description_index = 1; // 1-based.
136  traf_->header.flags = TrackFragmentHeader::kDefaultBaseIsMoofMask |
137  TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
138 
139  fragment_duration_ = 0;
140  earliest_presentation_time_ = kInvalidTime;
141  first_sap_time_ = kInvalidTime;
142  data_.reset(new BufferWriter());
143  key_frame_infos_.clear();
144  return Status::OK;
145 }
146 
148  if (stream_info_->is_encrypted()) {
149  Status status = FinalizeFragmentForEncryption();
150  if (!status.ok())
151  return status;
152  }
153 
154  if (first_fragment_) {
155  if (allow_adjust_earliest_presentation_time_) {
156  // Chrome (as of v66 https://crbug.com/398141) does not like negative
157  // values for adjusted dts = dts + Period@start (0 for first Period)
158  // - presentationTimeOffset
159  // Since |earliest_presentation_time| of the first fragment will be used
160  // to set presentationTimeOffset, the adjusted dts can become negative for
161  // the frames in the first segment in the first Period. To avoid seeing
162  // that, |earliest_presentation_time| is adjusted so it is not larger than
163  // the dts.
164  const int64_t dts = traf_->decode_time.decode_time;
165  if (earliest_presentation_time_ > dts) {
166  const uint64_t delta = earliest_presentation_time_ - dts;
167  earliest_presentation_time_ = dts;
168  fragment_duration_ += delta;
169  }
170  }
171  first_fragment_ = false;
172  }
173 
174  // Optimize trun box.
175  traf_->runs[0].sample_count =
176  static_cast<uint32_t>(traf_->runs[0].sample_sizes.size());
177  if (OptimizeSampleEntries(&traf_->runs[0].sample_durations,
178  &traf_->header.default_sample_duration)) {
179  traf_->header.flags |=
180  TrackFragmentHeader::kDefaultSampleDurationPresentMask;
181  } else {
182  traf_->runs[0].flags |= TrackFragmentRun::kSampleDurationPresentMask;
183  }
184  if (OptimizeSampleEntries(&traf_->runs[0].sample_sizes,
185  &traf_->header.default_sample_size)) {
186  traf_->header.flags |= TrackFragmentHeader::kDefaultSampleSizePresentMask;
187  } else {
188  traf_->runs[0].flags |= TrackFragmentRun::kSampleSizePresentMask;
189  }
190  if (OptimizeSampleEntries(&traf_->runs[0].sample_flags,
191  &traf_->header.default_sample_flags)) {
192  traf_->header.flags |= TrackFragmentHeader::kDefaultSampleFlagsPresentMask;
193  } else {
194  traf_->runs[0].flags |= TrackFragmentRun::kSampleFlagsPresentMask;
195  }
196 
197  // Add SampleToGroup boxes. A SampleToGroup box with grouping type of 'roll'
198  // needs to be added if there is seek preroll, referencing sample group
199  // description in track level; Also need to add SampleToGroup boxes
200  // correponding to every SampleGroupDescription boxes, referencing sample
201  // group description in fragment level.
202  DCHECK_EQ(traf_->sample_to_groups.size(), 0u);
203  if (seek_preroll_ > 0) {
204  traf_->sample_to_groups.resize(traf_->sample_to_groups.size() + 1);
205  SampleToGroup& sample_to_group = traf_->sample_to_groups.back();
206  sample_to_group.grouping_type = FOURCC_roll;
207 
208  sample_to_group.entries.resize(1);
209  SampleToGroupEntry& sample_to_group_entry = sample_to_group.entries.back();
210  sample_to_group_entry.sample_count = traf_->runs[0].sample_count;
211  sample_to_group_entry.group_description_index =
212  SampleToGroupEntry::kTrackGroupDescriptionIndexBase + 1;
213  }
214  for (const auto& sample_group_description :
215  traf_->sample_group_descriptions) {
216  traf_->sample_to_groups.resize(traf_->sample_to_groups.size() + 1);
217  SampleToGroup& sample_to_group = traf_->sample_to_groups.back();
218  sample_to_group.grouping_type = sample_group_description.grouping_type;
219 
220  sample_to_group.entries.resize(1);
221  SampleToGroupEntry& sample_to_group_entry = sample_to_group.entries.back();
222  sample_to_group_entry.sample_count = traf_->runs[0].sample_count;
223  sample_to_group_entry.group_description_index =
224  SampleToGroupEntry::kTrackFragmentGroupDescriptionIndexBase + 1;
225  }
226 
227  fragment_finalized_ = true;
228  fragment_initialized_ = false;
229  return Status::OK;
230 }
231 
233  // NOTE: Daisy chain is not supported currently.
234  reference->reference_type = false;
235  reference->subsegment_duration = fragment_duration_;
236  reference->starts_with_sap = StartsWithSAP();
237  if (kInvalidTime == first_sap_time_) {
238  reference->sap_type = SegmentReference::TypeUnknown;
239  reference->sap_delta_time = 0;
240  } else {
241  reference->sap_type = SegmentReference::Type1;
242  reference->sap_delta_time = first_sap_time_ - earliest_presentation_time_;
243  }
244  reference->earliest_presentation_time = earliest_presentation_time_;
245 }
246 
247 Status Fragmenter::FinalizeFragmentForEncryption() {
248  SampleEncryption& sample_encryption = traf_->sample_encryption;
249  if (sample_encryption.sample_encryption_entries.empty()) {
250  // This fragment is not encrypted.
251  // There are two sample description entries, an encrypted entry and a clear
252  // entry, are generated. The 1-based clear entry index is always 2.
253  const uint32_t kClearSampleDescriptionIndex = 2;
254  traf_->header.sample_description_index = kClearSampleDescriptionIndex;
255  return Status::OK;
256  }
257  if (sample_encryption.sample_encryption_entries.size() !=
258  traf_->runs[0].sample_sizes.size()) {
259  LOG(ERROR) << "Partially encrypted segment is not supported";
260  return Status(error::MUXER_FAILURE,
261  "Partially encrypted segment is not supported.");
262  }
263 
264  const SampleEncryptionEntry& sample_encryption_entry =
265  sample_encryption.sample_encryption_entries.front();
266  const bool use_subsample_encryption =
267  !sample_encryption_entry.subsamples.empty();
268  if (use_subsample_encryption)
269  traf_->sample_encryption.flags |= SampleEncryption::kUseSubsampleEncryption;
270  traf_->sample_encryption.iv_size = static_cast<uint8_t>(
271  sample_encryption_entry.initialization_vector.size());
272 
273  // The offset will be adjusted in Segmenter after knowing moof size.
274  traf_->auxiliary_offset.offsets.push_back(0);
275 
276  // Optimize saiz box.
277  SampleAuxiliaryInformationSize& saiz = traf_->auxiliary_size;
278  saiz.sample_count = static_cast<uint32_t>(saiz.sample_info_sizes.size());
279  DCHECK_EQ(saiz.sample_info_sizes.size(),
280  traf_->sample_encryption.sample_encryption_entries.size());
281  if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
282  &saiz.default_sample_info_size)) {
283  saiz.default_sample_info_size = 0;
284  }
285 
286  // It should only happen with full sample encryption + constant iv, i.e.
287  // 'cbcs' applying to audio.
288  if (saiz.default_sample_info_size == 0 && saiz.sample_info_sizes.empty()) {
289  DCHECK(!use_subsample_encryption);
290  // ISO/IEC 23001-7:2016(E) The sample auxiliary information would then be
291  // empty and should be omitted. Clear saiz and saio boxes so they are not
292  // written.
293  saiz.sample_count = 0;
294  traf_->auxiliary_offset.offsets.clear();
295  }
296  return Status::OK;
297 }
298 
299 bool Fragmenter::StartsWithSAP() const {
300  DCHECK(!traf_->runs.empty());
301  uint32_t start_sample_flag;
302  if (traf_->runs[0].flags & TrackFragmentRun::kSampleFlagsPresentMask) {
303  DCHECK(!traf_->runs[0].sample_flags.empty());
304  start_sample_flag = traf_->runs[0].sample_flags[0];
305  } else {
306  DCHECK(traf_->header.flags &
307  TrackFragmentHeader::kDefaultSampleFlagsPresentMask);
308  start_sample_flag = traf_->header.default_sample_flags;
309  }
310  return (start_sample_flag & TrackFragmentHeader::kNonKeySampleMask) == 0;
311 }
312 
313 } // namespace mp4
314 } // namespace media
315 } // namespace shaka
STL namespace.
All the methods that are virtual are virtual for mocking.
Status InitializeFragment(int64_t first_sample_dts)
Definition: fragmenter.cc:123
void GenerateSegmentReference(SegmentReference *reference) const
Fill reference with current fragment information.
Definition: fragmenter.cc:232
Fragmenter(std::shared_ptr< const StreamInfo > info, TrackFragment *traf)
Definition: fragmenter.cc:49
Status AddSample(const MediaSample &sample)
Definition: fragmenter.cc:66
Class to hold a media sample.
Definition: media_sample.h:22
Status FinalizeFragment()
Finalize and optimize the fragment.
Definition: fragmenter.cc:147
bool OptimizeSampleEntries(std::vector< T > *entries, T *default_value)
Definition: fragmenter.h:121