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