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