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