1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/mp4/track_run_iterator.h"
9 #include "media/base/buffers.h"
10 #include "media/base/stream_parser_buffer.h"
11 #include "media/mp4/rcheck.h"
14 static const uint32 kSampleIsDifferenceSampleFlagMask
= 0x10000;
29 std::vector
<SampleInfo
> samples
;
32 int64 sample_start_offset
;
35 const AudioSampleEntry
* audio_description
;
36 const VideoSampleEntry
* video_description
;
38 int64 aux_info_start_offset
; // Only valid if aux_info_total_size > 0.
39 int aux_info_default_size
;
40 std::vector
<uint8
> aux_info_sizes
; // Populated if default_size == 0.
41 int aux_info_total_size
;
47 TrackRunInfo::TrackRunInfo()
51 sample_start_offset(-1),
53 aux_info_start_offset(-1),
54 aux_info_default_size(-1),
55 aux_info_total_size(-1) {
57 TrackRunInfo::~TrackRunInfo() {}
59 TimeDelta
TimeDeltaFromRational(int64 numer
, int64 denom
) {
60 DCHECK_LT((numer
> 0 ? numer
: -numer
),
61 kint64max
/ base::Time::kMicrosecondsPerSecond
);
62 return TimeDelta::FromMicroseconds(
63 base::Time::kMicrosecondsPerSecond
* numer
/ denom
);
66 TrackRunIterator::TrackRunIterator(const Movie
* moov
,
68 : moov_(moov
), log_cb_(log_cb
), sample_offset_(0) {
72 TrackRunIterator::~TrackRunIterator() {}
74 static void PopulateSampleInfo(const TrackExtends
& trex
,
75 const TrackFragmentHeader
& tfhd
,
76 const TrackFragmentRun
& trun
,
77 const int64 edit_list_offset
,
79 SampleInfo
* sample_info
,
80 const SampleDependsOn sample_depends_on
) {
81 if (i
< trun
.sample_sizes
.size()) {
82 sample_info
->size
= trun
.sample_sizes
[i
];
83 } else if (tfhd
.default_sample_size
> 0) {
84 sample_info
->size
= tfhd
.default_sample_size
;
86 sample_info
->size
= trex
.default_sample_size
;
89 if (i
< trun
.sample_durations
.size()) {
90 sample_info
->duration
= trun
.sample_durations
[i
];
91 } else if (tfhd
.default_sample_duration
> 0) {
92 sample_info
->duration
= tfhd
.default_sample_duration
;
94 sample_info
->duration
= trex
.default_sample_duration
;
97 if (i
< trun
.sample_composition_time_offsets
.size()) {
98 sample_info
->cts_offset
= trun
.sample_composition_time_offsets
[i
];
100 sample_info
->cts_offset
= 0;
102 sample_info
->cts_offset
+= edit_list_offset
;
105 if (i
< trun
.sample_flags
.size()) {
106 flags
= trun
.sample_flags
[i
];
107 } else if (tfhd
.has_default_sample_flags
) {
108 flags
= tfhd
.default_sample_flags
;
110 flags
= trex
.default_sample_flags
;
113 switch (sample_depends_on
) {
114 case kSampleDependsOnUnknown
:
115 sample_info
->is_keyframe
= !(flags
& kSampleIsDifferenceSampleFlagMask
);
118 case kSampleDependsOnOthers
:
119 sample_info
->is_keyframe
= false;
122 case kSampleDependsOnNoOther
:
123 sample_info
->is_keyframe
= true;
126 case kSampleDependsOnReserved
:
131 // In well-structured encrypted media, each track run will be immediately
132 // preceded by its auxiliary information; this is the only optimal storage
133 // pattern in terms of minimum number of bytes from a serial stream needed to
134 // begin playback. It also allows us to optimize caching on memory-constrained
135 // architectures, because we can cache the relatively small auxiliary
136 // information for an entire run and then discard data from the input stream,
137 // instead of retaining the entire 'mdat' box.
139 // We optimize for this situation (with no loss of generality) by sorting track
140 // runs during iteration in order of their first data offset (either sample data
141 // or auxiliary data).
142 class CompareMinTrackRunDataOffset
{
144 bool operator()(const TrackRunInfo
& a
, const TrackRunInfo
& b
) {
145 int64 a_aux
= a
.aux_info_total_size
? a
.aux_info_start_offset
: kint64max
;
146 int64 b_aux
= b
.aux_info_total_size
? b
.aux_info_start_offset
: kint64max
;
148 int64 a_lesser
= std::min(a_aux
, a
.sample_start_offset
);
149 int64 a_greater
= std::max(a_aux
, a
.sample_start_offset
);
150 int64 b_lesser
= std::min(b_aux
, b
.sample_start_offset
);
151 int64 b_greater
= std::max(b_aux
, b
.sample_start_offset
);
153 if (a_lesser
== b_lesser
) return a_greater
< b_greater
;
154 return a_lesser
< b_lesser
;
158 bool TrackRunIterator::Init(const MovieFragment
& moof
) {
161 for (size_t i
= 0; i
< moof
.tracks
.size(); i
++) {
162 const TrackFragment
& traf
= moof
.tracks
[i
];
164 const Track
* trak
= NULL
;
165 for (size_t t
= 0; t
< moov_
->tracks
.size(); t
++) {
166 if (moov_
->tracks
[t
].header
.track_id
== traf
.header
.track_id
)
167 trak
= &moov_
->tracks
[t
];
171 const TrackExtends
* trex
= NULL
;
172 for (size_t t
= 0; t
< moov_
->extends
.tracks
.size(); t
++) {
173 if (moov_
->extends
.tracks
[t
].track_id
== traf
.header
.track_id
)
174 trex
= &moov_
->extends
.tracks
[t
];
178 const SampleDescription
& stsd
=
179 trak
->media
.information
.sample_table
.description
;
180 if (stsd
.type
!= kAudio
&& stsd
.type
!= kVideo
) {
181 DVLOG(1) << "Skipping unhandled track type";
184 size_t desc_idx
= traf
.header
.sample_description_index
;
185 if (!desc_idx
) desc_idx
= trex
->default_sample_description_index
;
186 RCHECK(desc_idx
> 0); // Descriptions are one-indexed in the file
189 // Process edit list to remove CTS offset introduced in the presence of
190 // B-frames (those that contain a single edit with a nonnegative media
191 // time). Other uses of edit lists are not supported, as they are
192 // both uncommon and better served by higher-level protocols.
193 int64 edit_list_offset
= 0;
194 const std::vector
<EditListEntry
>& edits
= trak
->edit
.list
.edits
;
195 if (!edits
.empty()) {
196 if (edits
.size() > 1)
197 DVLOG(1) << "Multi-entry edit box detected; some components ignored.";
199 if (edits
[0].media_time
< 0) {
200 DVLOG(1) << "Empty edit list entry ignored.";
202 edit_list_offset
= -edits
[0].media_time
;
206 int64 run_start_dts
= traf
.decode_time
.decode_time
;
207 int sample_count_sum
= 0;
208 bool is_sync_sample_box_present
=
209 trak
->media
.information
.sample_table
.sync_sample
.is_present
;
210 for (size_t j
= 0; j
< traf
.runs
.size(); j
++) {
211 const TrackFragmentRun
& trun
= traf
.runs
[j
];
213 tri
.track_id
= traf
.header
.track_id
;
214 tri
.timescale
= trak
->media
.header
.timescale
;
215 tri
.start_dts
= run_start_dts
;
216 tri
.sample_start_offset
= trun
.data_offset
;
218 tri
.is_audio
= (stsd
.type
== kAudio
);
220 RCHECK(!stsd
.audio_entries
.empty());
221 if (desc_idx
> stsd
.audio_entries
.size())
223 tri
.audio_description
= &stsd
.audio_entries
[desc_idx
];
225 RCHECK(!stsd
.video_entries
.empty());
226 if (desc_idx
> stsd
.video_entries
.size())
228 tri
.video_description
= &stsd
.video_entries
[desc_idx
];
231 // Collect information from the auxiliary_offset entry with the same index
232 // in the 'saiz' container as the current run's index in the 'trun'
233 // container, if it is present.
234 if (traf
.auxiliary_offset
.offsets
.size() > j
) {
235 // There should be an auxiliary info entry corresponding to each sample
236 // in the auxiliary offset entry's corresponding track run.
237 RCHECK(traf
.auxiliary_size
.sample_count
>=
238 sample_count_sum
+ trun
.sample_count
);
239 tri
.aux_info_start_offset
= traf
.auxiliary_offset
.offsets
[j
];
240 tri
.aux_info_default_size
=
241 traf
.auxiliary_size
.default_sample_info_size
;
242 if (tri
.aux_info_default_size
== 0) {
243 const std::vector
<uint8
>& sizes
=
244 traf
.auxiliary_size
.sample_info_sizes
;
245 tri
.aux_info_sizes
.insert(tri
.aux_info_sizes
.begin(),
246 sizes
.begin() + sample_count_sum
,
247 sizes
.begin() + sample_count_sum
+ trun
.sample_count
);
250 // If the default info size is positive, find the total size of the aux
251 // info block from it, otherwise sum over the individual sizes of each
252 // aux info entry in the aux_offset entry.
253 if (tri
.aux_info_default_size
) {
254 tri
.aux_info_total_size
=
255 tri
.aux_info_default_size
* trun
.sample_count
;
257 tri
.aux_info_total_size
= 0;
258 for (size_t k
= 0; k
< trun
.sample_count
; k
++) {
259 tri
.aux_info_total_size
+= tri
.aux_info_sizes
[k
];
263 tri
.aux_info_start_offset
= -1;
264 tri
.aux_info_total_size
= 0;
267 tri
.samples
.resize(trun
.sample_count
);
268 for (size_t k
= 0; k
< trun
.sample_count
; k
++) {
269 PopulateSampleInfo(*trex
, traf
.header
, trun
, edit_list_offset
,
270 k
, &tri
.samples
[k
], traf
.sdtp
.sample_depends_on(k
));
271 run_start_dts
+= tri
.samples
[k
].duration
;
273 // ISO-14496-12 Section 8.20.1 : If the sync sample box is not present,
274 // every sample is a random access point.
276 // NOTE: MPEG's "is random access point" concept is equivalent to this
277 // and downstream code's "is keyframe" concept.
278 if (!is_sync_sample_box_present
)
279 tri
.samples
[k
].is_keyframe
= true;
281 runs_
.push_back(tri
);
282 sample_count_sum
+= trun
.sample_count
;
286 std::sort(runs_
.begin(), runs_
.end(), CompareMinTrackRunDataOffset());
287 run_itr_
= runs_
.begin();
292 void TrackRunIterator::AdvanceRun() {
297 void TrackRunIterator::ResetRun() {
298 if (!IsRunValid()) return;
299 sample_dts_
= run_itr_
->start_dts
;
300 sample_offset_
= run_itr_
->sample_start_offset
;
301 sample_itr_
= run_itr_
->samples
.begin();
305 void TrackRunIterator::AdvanceSample() {
306 DCHECK(IsSampleValid());
307 sample_dts_
+= sample_itr_
->duration
;
308 sample_offset_
+= sample_itr_
->size
;
312 // This implementation only indicates a need for caching if CENC auxiliary
313 // info is available in the stream.
314 bool TrackRunIterator::AuxInfoNeedsToBeCached() {
315 DCHECK(IsRunValid());
316 return is_encrypted() && aux_info_size() > 0 && cenc_info_
.size() == 0;
319 // This implementation currently only caches CENC auxiliary info.
320 bool TrackRunIterator::CacheAuxInfo(const uint8
* buf
, int buf_size
) {
321 RCHECK(AuxInfoNeedsToBeCached() && buf_size
>= aux_info_size());
323 cenc_info_
.resize(run_itr_
->samples
.size());
325 for (size_t i
= 0; i
< run_itr_
->samples
.size(); i
++) {
326 int info_size
= run_itr_
->aux_info_default_size
;
328 info_size
= run_itr_
->aux_info_sizes
[i
];
330 BufferReader
reader(buf
+ pos
, info_size
);
331 RCHECK(cenc_info_
[i
].Parse(track_encryption().default_iv_size
, &reader
));
338 bool TrackRunIterator::IsRunValid() const {
339 return run_itr_
!= runs_
.end();
342 bool TrackRunIterator::IsSampleValid() const {
343 return IsRunValid() && (sample_itr_
!= run_itr_
->samples
.end());
346 // Because tracks are in sorted order and auxiliary information is cached when
347 // returning samples, it is guaranteed that no data will be required before the
348 // lesser of the minimum data offset of this track and the next in sequence.
349 // (The stronger condition - that no data is required before the minimum data
350 // offset of this track alone - is not guaranteed, because the BMFF spec does
351 // not have any inter-run ordering restrictions.)
352 int64
TrackRunIterator::GetMaxClearOffset() {
353 int64 offset
= kint64max
;
355 if (IsSampleValid()) {
356 offset
= std::min(offset
, sample_offset_
);
357 if (AuxInfoNeedsToBeCached())
358 offset
= std::min(offset
, aux_info_offset());
360 if (run_itr_
!= runs_
.end()) {
361 std::vector
<TrackRunInfo
>::const_iterator next_run
= run_itr_
+ 1;
362 if (next_run
!= runs_
.end()) {
363 offset
= std::min(offset
, next_run
->sample_start_offset
);
364 if (next_run
->aux_info_total_size
)
365 offset
= std::min(offset
, next_run
->aux_info_start_offset
);
368 if (offset
== kint64max
) return 0;
372 uint32
TrackRunIterator::track_id() const {
373 DCHECK(IsRunValid());
374 return run_itr_
->track_id
;
377 bool TrackRunIterator::is_encrypted() const {
378 DCHECK(IsRunValid());
379 return track_encryption().is_encrypted
;
382 int64
TrackRunIterator::aux_info_offset() const {
383 return run_itr_
->aux_info_start_offset
;
386 int TrackRunIterator::aux_info_size() const {
387 return run_itr_
->aux_info_total_size
;
390 bool TrackRunIterator::is_audio() const {
391 DCHECK(IsRunValid());
392 return run_itr_
->is_audio
;
395 const AudioSampleEntry
& TrackRunIterator::audio_description() const {
397 DCHECK(run_itr_
->audio_description
);
398 return *run_itr_
->audio_description
;
401 const VideoSampleEntry
& TrackRunIterator::video_description() const {
403 DCHECK(run_itr_
->video_description
);
404 return *run_itr_
->video_description
;
407 int64
TrackRunIterator::sample_offset() const {
408 DCHECK(IsSampleValid());
409 return sample_offset_
;
412 int TrackRunIterator::sample_size() const {
413 DCHECK(IsSampleValid());
414 return sample_itr_
->size
;
417 TimeDelta
TrackRunIterator::dts() const {
418 DCHECK(IsSampleValid());
419 return TimeDeltaFromRational(sample_dts_
, run_itr_
->timescale
);
422 TimeDelta
TrackRunIterator::cts() const {
423 DCHECK(IsSampleValid());
424 return TimeDeltaFromRational(sample_dts_
+ sample_itr_
->cts_offset
,
425 run_itr_
->timescale
);
428 TimeDelta
TrackRunIterator::duration() const {
429 DCHECK(IsSampleValid());
430 return TimeDeltaFromRational(sample_itr_
->duration
, run_itr_
->timescale
);
433 bool TrackRunIterator::is_keyframe() const {
434 DCHECK(IsSampleValid());
435 return sample_itr_
->is_keyframe
;
438 const TrackEncryption
& TrackRunIterator::track_encryption() const {
440 return audio_description().sinf
.info
.track_encryption
;
441 return video_description().sinf
.info
.track_encryption
;
444 scoped_ptr
<DecryptConfig
> TrackRunIterator::GetDecryptConfig() {
445 size_t sample_idx
= sample_itr_
- run_itr_
->samples
.begin();
446 DCHECK(sample_idx
< cenc_info_
.size());
447 const FrameCENCInfo
& cenc_info
= cenc_info_
[sample_idx
];
448 DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached());
450 size_t total_size
= 0;
451 if (!cenc_info
.subsamples
.empty() &&
452 (!cenc_info
.GetTotalSizeOfSubsamples(&total_size
) ||
453 total_size
!= static_cast<size_t>(sample_size()))) {
454 MEDIA_LOG(log_cb_
) << "Incorrect CENC subsample size.";
455 return scoped_ptr
<DecryptConfig
>();
458 const std::vector
<uint8
>& kid
= track_encryption().default_kid
;
459 return scoped_ptr
<DecryptConfig
>(new DecryptConfig(
460 std::string(reinterpret_cast<const char*>(&kid
[0]), kid
.size()),
461 std::string(reinterpret_cast<const char*>(cenc_info
.iv
),
462 arraysize(cenc_info
.iv
)),
463 cenc_info
.subsamples
));