content/media/fmp4/demuxer/track_run_iterator.cc

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

michael@0 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
michael@0 2 // Use of this source code is governed by a BSD-style license that can be
michael@0 3 // found in the LICENSE file.
michael@0 4
michael@0 5 #include "mp4_demuxer/track_run_iterator.h"
michael@0 6 #include "mp4_demuxer/basictypes.h"
michael@0 7 #include "mp4_demuxer/Streams.h"
michael@0 8
michael@0 9 #include <algorithm>
michael@0 10 #include <memory>
michael@0 11 #include <assert.h>
michael@0 12
michael@0 13 using namespace std;
michael@0 14
michael@0 15 namespace mp4_demuxer {
michael@0 16
michael@0 17 static const uint32_t kSampleIsDifferenceSampleFlagMask = 0x10000;
michael@0 18
michael@0 19 struct SampleInfo {
michael@0 20 int size;
michael@0 21 int duration;
michael@0 22 int cts_offset;
michael@0 23 bool is_keyframe;
michael@0 24 };
michael@0 25
michael@0 26 struct TrackRunInfo {
michael@0 27 uint32_t track_id;
michael@0 28 std::vector<SampleInfo> samples;
michael@0 29 int64_t timescale;
michael@0 30 int64_t start_dts;
michael@0 31 int64_t sample_start_offset;
michael@0 32
michael@0 33 bool is_audio;
michael@0 34 const AudioSampleEntry* audio_description;
michael@0 35 const VideoSampleEntry* video_description;
michael@0 36
michael@0 37 int64_t aux_info_start_offset; // Only valid if aux_info_total_size > 0.
michael@0 38 int aux_info_default_size;
michael@0 39 std::vector<uint8_t> aux_info_sizes; // Populated if default_size == 0.
michael@0 40 int aux_info_total_size;
michael@0 41
michael@0 42 TrackRunInfo();
michael@0 43 ~TrackRunInfo();
michael@0 44 };
michael@0 45
michael@0 46 TrackRunInfo::TrackRunInfo()
michael@0 47 : track_id(0),
michael@0 48 timescale(-1),
michael@0 49 start_dts(-1),
michael@0 50 sample_start_offset(-1),
michael@0 51 is_audio(false),
michael@0 52 aux_info_start_offset(-1),
michael@0 53 aux_info_default_size(-1),
michael@0 54 aux_info_total_size(-1) {
michael@0 55 }
michael@0 56 TrackRunInfo::~TrackRunInfo() {}
michael@0 57
michael@0 58 Microseconds TimeDeltaFromRational(int64_t numer, int64_t denom) {
michael@0 59 DCHECK_LT((numer > 0 ? numer : -numer),
michael@0 60 kint64max / MicrosecondsPerSecond);
michael@0 61 return MicrosecondsPerSecond * numer / denom;
michael@0 62 }
michael@0 63
michael@0 64 TrackRunIterator::TrackRunIterator(const Movie* moov)
michael@0 65 : moov_(moov), sample_offset_(0) {
michael@0 66 CHECK(moov);
michael@0 67 }
michael@0 68
michael@0 69 TrackRunIterator::~TrackRunIterator() {}
michael@0 70
michael@0 71 static void PopulateSampleInfo(const TrackExtends& trex,
michael@0 72 const TrackFragmentHeader& tfhd,
michael@0 73 const TrackFragmentRun& trun,
michael@0 74 const int64_t edit_list_offset,
michael@0 75 const uint32_t i,
michael@0 76 SampleInfo* sample_info) {
michael@0 77 if (i < trun.sample_sizes.size()) {
michael@0 78 sample_info->size = trun.sample_sizes[i];
michael@0 79 } else if (tfhd.default_sample_size > 0) {
michael@0 80 sample_info->size = tfhd.default_sample_size;
michael@0 81 } else {
michael@0 82 sample_info->size = trex.default_sample_size;
michael@0 83 }
michael@0 84
michael@0 85 if (i < trun.sample_durations.size()) {
michael@0 86 sample_info->duration = trun.sample_durations[i];
michael@0 87 } else if (tfhd.default_sample_duration > 0) {
michael@0 88 sample_info->duration = tfhd.default_sample_duration;
michael@0 89 } else {
michael@0 90 sample_info->duration = trex.default_sample_duration;
michael@0 91 }
michael@0 92
michael@0 93 if (i < trun.sample_composition_time_offsets.size()) {
michael@0 94 sample_info->cts_offset = trun.sample_composition_time_offsets[i];
michael@0 95 } else {
michael@0 96 sample_info->cts_offset = 0;
michael@0 97 }
michael@0 98 sample_info->cts_offset += edit_list_offset;
michael@0 99
michael@0 100 uint32_t flags;
michael@0 101 if (i < trun.sample_flags.size()) {
michael@0 102 flags = trun.sample_flags[i];
michael@0 103 } else if (tfhd.has_default_sample_flags) {
michael@0 104 flags = tfhd.default_sample_flags;
michael@0 105 } else {
michael@0 106 flags = trex.default_sample_flags;
michael@0 107 }
michael@0 108 sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask);
michael@0 109 }
michael@0 110
michael@0 111 // In well-structured encrypted media, each track run will be immediately
michael@0 112 // preceded by its auxiliary information; this is the only optimal storage
michael@0 113 // pattern in terms of minimum number of bytes from a serial stream needed to
michael@0 114 // begin playback. It also allows us to optimize caching on memory-constrained
michael@0 115 // architectures, because we can cache the relatively small auxiliary
michael@0 116 // information for an entire run and then discard data from the input stream,
michael@0 117 // instead of retaining the entire 'mdat' box.
michael@0 118 //
michael@0 119 // We optimize for this situation (with no loss of generality) by sorting track
michael@0 120 // runs during iteration in order of their first data offset (either sample data
michael@0 121 // or auxiliary data).
michael@0 122 class CompareMinTrackRunDataOffset {
michael@0 123 public:
michael@0 124 bool operator()(const TrackRunInfo& a, const TrackRunInfo& b) {
michael@0 125 int64_t a_aux = a.aux_info_total_size ? a.aux_info_start_offset : kint64max;
michael@0 126 int64_t b_aux = b.aux_info_total_size ? b.aux_info_start_offset : kint64max;
michael@0 127
michael@0 128 int64_t a_lesser = std::min(a_aux, a.sample_start_offset);
michael@0 129 int64_t a_greater = std::max(a_aux, a.sample_start_offset);
michael@0 130 int64_t b_lesser = std::min(b_aux, b.sample_start_offset);
michael@0 131 int64_t b_greater = std::max(b_aux, b.sample_start_offset);
michael@0 132
michael@0 133 if (a_lesser == b_lesser) return a_greater < b_greater;
michael@0 134 return a_lesser < b_lesser;
michael@0 135 }
michael@0 136 };
michael@0 137
michael@0 138 bool TrackRunIterator::Init(const MovieFragment& moof) {
michael@0 139 runs_.clear();
michael@0 140
michael@0 141 for (size_t i = 0; i < moof.tracks.size(); i++) {
michael@0 142 const TrackFragment& traf = moof.tracks[i];
michael@0 143
michael@0 144 const Track* trak = NULL;
michael@0 145 for (size_t t = 0; t < moov_->tracks.size(); t++) {
michael@0 146 if (moov_->tracks[t].header.track_id == traf.header.track_id)
michael@0 147 trak = &moov_->tracks[t];
michael@0 148 }
michael@0 149 RCHECK(trak);
michael@0 150
michael@0 151 const TrackExtends* trex = NULL;
michael@0 152 for (size_t t = 0; t < moov_->extends.tracks.size(); t++) {
michael@0 153 if (moov_->extends.tracks[t].track_id == traf.header.track_id)
michael@0 154 trex = &moov_->extends.tracks[t];
michael@0 155 }
michael@0 156 RCHECK(trex);
michael@0 157
michael@0 158 const SampleDescription& stsd =
michael@0 159 trak->media.information.sample_table.description;
michael@0 160 if (stsd.type != kAudio && stsd.type != kVideo) {
michael@0 161 DMX_LOG("Skipping unhandled track type\n");
michael@0 162 continue;
michael@0 163 }
michael@0 164 size_t desc_idx = traf.header.sample_description_index;
michael@0 165 if (!desc_idx) desc_idx = trex->default_sample_description_index;
michael@0 166 RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file
michael@0 167 desc_idx -= 1;
michael@0 168
michael@0 169 // Process edit list to remove CTS offset introduced in the presence of
michael@0 170 // B-frames (those that contain a single edit with a nonnegative media
michael@0 171 // time). Other uses of edit lists are not supported, as they are
michael@0 172 // both uncommon and better served by higher-level protocols.
michael@0 173 int64_t edit_list_offset = 0;
michael@0 174 const std::vector<EditListEntry>& edits = trak->edit.list.edits;
michael@0 175 if (!edits.empty()) {
michael@0 176 if (edits.size() > 1)
michael@0 177 DMX_LOG("Multi-entry edit box detected; some components ignored.\n");
michael@0 178
michael@0 179 if (edits[0].media_time < 0) {
michael@0 180 DMX_LOG("Empty edit list entry ignored.\n");
michael@0 181 } else {
michael@0 182 edit_list_offset = -edits[0].media_time;
michael@0 183 }
michael@0 184 }
michael@0 185
michael@0 186 int64_t run_start_dts = traf.decode_time.decode_time;
michael@0 187 int sample_count_sum = 0;
michael@0 188 for (size_t j = 0; j < traf.runs.size(); j++) {
michael@0 189 const TrackFragmentRun& trun = traf.runs[j];
michael@0 190 TrackRunInfo tri;
michael@0 191 tri.track_id = traf.header.track_id;
michael@0 192 tri.timescale = trak->media.header.timescale;
michael@0 193 tri.start_dts = run_start_dts;
michael@0 194 tri.sample_start_offset = trun.data_offset;
michael@0 195
michael@0 196 tri.is_audio = (stsd.type == kAudio);
michael@0 197 if (tri.is_audio) {
michael@0 198 RCHECK(!stsd.audio_entries.empty());
michael@0 199 if (desc_idx > stsd.audio_entries.size())
michael@0 200 desc_idx = 0;
michael@0 201 tri.audio_description = &stsd.audio_entries[desc_idx];
michael@0 202 } else {
michael@0 203 RCHECK(!stsd.video_entries.empty());
michael@0 204 if (desc_idx > stsd.video_entries.size())
michael@0 205 desc_idx = 0;
michael@0 206 tri.video_description = &stsd.video_entries[desc_idx];
michael@0 207 }
michael@0 208
michael@0 209 // Collect information from the auxiliary_offset entry with the same index
michael@0 210 // in the 'saiz' container as the current run's index in the 'trun'
michael@0 211 // container, if it is present.
michael@0 212 if (traf.auxiliary_offset.offsets.size() > j) {
michael@0 213 // There should be an auxiliary info entry corresponding to each sample
michael@0 214 // in the auxiliary offset entry's corresponding track run.
michael@0 215 RCHECK(traf.auxiliary_size.sample_count >=
michael@0 216 sample_count_sum + trun.sample_count);
michael@0 217 tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j];
michael@0 218 tri.aux_info_default_size =
michael@0 219 traf.auxiliary_size.default_sample_info_size;
michael@0 220 if (tri.aux_info_default_size == 0) {
michael@0 221 const std::vector<uint8_t>& sizes =
michael@0 222 traf.auxiliary_size.sample_info_sizes;
michael@0 223 tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(),
michael@0 224 sizes.begin() + sample_count_sum,
michael@0 225 sizes.begin() + sample_count_sum + trun.sample_count);
michael@0 226 }
michael@0 227
michael@0 228 // If the default info size is positive, find the total size of the aux
michael@0 229 // info block from it, otherwise sum over the individual sizes of each
michael@0 230 // aux info entry in the aux_offset entry.
michael@0 231 if (tri.aux_info_default_size) {
michael@0 232 tri.aux_info_total_size =
michael@0 233 tri.aux_info_default_size * trun.sample_count;
michael@0 234 } else {
michael@0 235 tri.aux_info_total_size = 0;
michael@0 236 for (size_t k = 0; k < trun.sample_count; k++) {
michael@0 237 tri.aux_info_total_size += tri.aux_info_sizes[k];
michael@0 238 }
michael@0 239 }
michael@0 240 } else {
michael@0 241 tri.aux_info_start_offset = -1;
michael@0 242 tri.aux_info_total_size = 0;
michael@0 243 }
michael@0 244
michael@0 245 tri.samples.resize(trun.sample_count);
michael@0 246 for (size_t k = 0; k < trun.sample_count; k++) {
michael@0 247 PopulateSampleInfo(*trex, traf.header, trun, edit_list_offset,
michael@0 248 k, &tri.samples[k]);
michael@0 249 run_start_dts += tri.samples[k].duration;
michael@0 250 }
michael@0 251 runs_.push_back(tri);
michael@0 252 sample_count_sum += trun.sample_count;
michael@0 253 }
michael@0 254 }
michael@0 255
michael@0 256 std::sort(runs_.begin(), runs_.end(), CompareMinTrackRunDataOffset());
michael@0 257 run_itr_ = runs_.begin();
michael@0 258 ResetRun();
michael@0 259 return true;
michael@0 260 }
michael@0 261
michael@0 262 void TrackRunIterator::AdvanceRun() {
michael@0 263 ++run_itr_;
michael@0 264 ResetRun();
michael@0 265 }
michael@0 266
michael@0 267 void TrackRunIterator::ResetRun() {
michael@0 268 if (!IsRunValid()) return;
michael@0 269 sample_dts_ = run_itr_->start_dts;
michael@0 270 sample_offset_ = run_itr_->sample_start_offset;
michael@0 271 sample_itr_ = run_itr_->samples.begin();
michael@0 272 cenc_info_.clear();
michael@0 273 }
michael@0 274
michael@0 275 void TrackRunIterator::AdvanceSample() {
michael@0 276 DCHECK(IsSampleValid());
michael@0 277 sample_dts_ += sample_itr_->duration;
michael@0 278 sample_offset_ += sample_itr_->size;
michael@0 279 ++sample_itr_;
michael@0 280 }
michael@0 281
michael@0 282 // This implementation only indicates a need for caching if CENC auxiliary
michael@0 283 // info is available in the stream.
michael@0 284 bool TrackRunIterator::AuxInfoNeedsToBeCached() {
michael@0 285 DCHECK(IsRunValid());
michael@0 286 return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0;
michael@0 287 }
michael@0 288
michael@0 289 // This implementation currently only caches CENC auxiliary info.
michael@0 290 bool TrackRunIterator::CacheAuxInfo(Stream* stream, int64_t moof_offset) {
michael@0 291 RCHECK(AuxInfoNeedsToBeCached());
michael@0 292
michael@0 293 int64_t offset = aux_info_offset() + moof_offset;
michael@0 294 if (stream->Length() - offset < aux_info_size()) {
michael@0 295 return false;
michael@0 296 }
michael@0 297
michael@0 298 assert(run_itr_ == runs_.begin());
michael@0 299 cenc_info_.resize(run_itr_->samples.size());
michael@0 300 int64_t pos = 0;
michael@0 301 for (size_t i = 0; i < run_itr_->samples.size(); i++) {
michael@0 302 int info_size = run_itr_->aux_info_default_size;
michael@0 303 if (!info_size)
michael@0 304 info_size = run_itr_->aux_info_sizes[i];
michael@0 305
michael@0 306 StreamReader reader(stream, offset + pos, info_size);
michael@0 307 RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader));
michael@0 308 pos += info_size;
michael@0 309 }
michael@0 310
michael@0 311 return true;
michael@0 312 }
michael@0 313
michael@0 314 bool TrackRunIterator::IsRunValid() const {
michael@0 315 return run_itr_ != runs_.end();
michael@0 316 }
michael@0 317
michael@0 318 bool TrackRunIterator::IsSampleValid() const {
michael@0 319 return IsRunValid() && (sample_itr_ != run_itr_->samples.end());
michael@0 320 }
michael@0 321
michael@0 322 // Because tracks are in sorted order and auxiliary information is cached when
michael@0 323 // returning samples, it is guaranteed that no data will be required before the
michael@0 324 // lesser of the minimum data offset of this track and the next in sequence.
michael@0 325 // (The stronger condition - that no data is required before the minimum data
michael@0 326 // offset of this track alone - is not guaranteed, because the BMFF spec does
michael@0 327 // not have any inter-run ordering restrictions.)
michael@0 328 int64_t TrackRunIterator::GetMaxClearOffset() {
michael@0 329 int64_t offset = kint64max;
michael@0 330
michael@0 331 if (IsSampleValid()) {
michael@0 332 offset = std::min(offset, sample_offset_);
michael@0 333 if (AuxInfoNeedsToBeCached())
michael@0 334 offset = std::min(offset, aux_info_offset());
michael@0 335 }
michael@0 336 if (run_itr_ != runs_.end()) {
michael@0 337 std::vector<TrackRunInfo>::const_iterator next_run = run_itr_ + 1;
michael@0 338 if (next_run != runs_.end()) {
michael@0 339 offset = std::min(offset, next_run->sample_start_offset);
michael@0 340 if (next_run->aux_info_total_size)
michael@0 341 offset = std::min(offset, next_run->aux_info_start_offset);
michael@0 342 }
michael@0 343 }
michael@0 344 if (offset == kint64max) return 0;
michael@0 345 return offset;
michael@0 346 }
michael@0 347
michael@0 348 Microseconds TrackRunIterator::GetMinDecodeTimestamp() {
michael@0 349 Microseconds dts = -1;
michael@0 350 for (size_t i = 0; i < runs_.size(); i++) {
michael@0 351 dts = std::min(dts, MicrosecondsFromRational(runs_[i].start_dts,
michael@0 352 runs_[i].timescale));
michael@0 353 }
michael@0 354 return dts;
michael@0 355 }
michael@0 356
michael@0 357 uint32_t TrackRunIterator::track_id() const {
michael@0 358 DCHECK(IsRunValid());
michael@0 359 return run_itr_->track_id;
michael@0 360 }
michael@0 361
michael@0 362 bool TrackRunIterator::is_encrypted() const {
michael@0 363 DCHECK(IsRunValid());
michael@0 364 return track_encryption().is_encrypted;
michael@0 365 }
michael@0 366
michael@0 367 int64_t TrackRunIterator::aux_info_offset() const {
michael@0 368 return run_itr_->aux_info_start_offset;
michael@0 369 }
michael@0 370
michael@0 371 int TrackRunIterator::aux_info_size() const {
michael@0 372 return run_itr_->aux_info_total_size;
michael@0 373 }
michael@0 374
michael@0 375 bool TrackRunIterator::is_audio() const {
michael@0 376 DCHECK(IsRunValid());
michael@0 377 return run_itr_->is_audio;
michael@0 378 }
michael@0 379
michael@0 380 const AudioSampleEntry& TrackRunIterator::audio_description() const {
michael@0 381 DCHECK(is_audio());
michael@0 382 DCHECK(run_itr_->audio_description);
michael@0 383 return *run_itr_->audio_description;
michael@0 384 }
michael@0 385
michael@0 386 const VideoSampleEntry& TrackRunIterator::video_description() const {
michael@0 387 DCHECK(!is_audio());
michael@0 388 DCHECK(run_itr_->video_description);
michael@0 389 return *run_itr_->video_description;
michael@0 390 }
michael@0 391
michael@0 392 int64_t TrackRunIterator::sample_offset() const {
michael@0 393 DCHECK(IsSampleValid());
michael@0 394 return sample_offset_;
michael@0 395 }
michael@0 396
michael@0 397 int TrackRunIterator::sample_size() const {
michael@0 398 DCHECK(IsSampleValid());
michael@0 399 return sample_itr_->size;
michael@0 400 }
michael@0 401
michael@0 402 Microseconds TrackRunIterator::dts() const {
michael@0 403 DCHECK(IsSampleValid());
michael@0 404 return MicrosecondsFromRational(sample_dts_, run_itr_->timescale);
michael@0 405 }
michael@0 406
michael@0 407 Microseconds TrackRunIterator::cts() const {
michael@0 408 DCHECK(IsSampleValid());
michael@0 409 return MicrosecondsFromRational(sample_dts_ + sample_itr_->cts_offset,
michael@0 410 run_itr_->timescale);
michael@0 411 }
michael@0 412
michael@0 413 Microseconds TrackRunIterator::duration() const {
michael@0 414 DCHECK(IsSampleValid());
michael@0 415 return MicrosecondsFromRational(sample_itr_->duration, run_itr_->timescale);
michael@0 416 }
michael@0 417
michael@0 418 bool TrackRunIterator::is_keyframe() const {
michael@0 419 DCHECK(IsSampleValid());
michael@0 420 return sample_itr_->is_keyframe;
michael@0 421 }
michael@0 422
michael@0 423 const TrackEncryption& TrackRunIterator::track_encryption() const {
michael@0 424 if (is_audio())
michael@0 425 return audio_description().sinf.info.track_encryption;
michael@0 426 return video_description().sinf.info.track_encryption;
michael@0 427 }
michael@0 428
michael@0 429 void TrackRunIterator::GetDecryptConfig(nsAutoPtr<DecryptConfig>& config) {
michael@0 430 size_t sample_idx = sample_itr_ - run_itr_->samples.begin();
michael@0 431 DCHECK(sample_idx < cenc_info_.size());
michael@0 432 const FrameCENCInfo& cenc_info = cenc_info_[sample_idx];
michael@0 433 DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached());
michael@0 434
michael@0 435 if (!cenc_info.subsamples.empty() &&
michael@0 436 (cenc_info.GetTotalSizeOfSubsamples() !=
michael@0 437 static_cast<size_t>(sample_size()))) {
michael@0 438 DMX_LOG("Incorrect CENC subsample size.\n");
michael@0 439 return;
michael@0 440 }
michael@0 441
michael@0 442 const std::vector<uint8_t>& kid = track_encryption().default_kid;
michael@0 443 config = new DecryptConfig(
michael@0 444 std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()),
michael@0 445 std::string(reinterpret_cast<const char*>(cenc_info.iv),
michael@0 446 arraysize(cenc_info.iv)),
michael@0 447 0, // No offset to start of media data in MP4 using CENC.
michael@0 448 cenc_info.subsamples);
michael@0 449 }
michael@0 450
michael@0 451 } // namespace mp4_demuxer

mercurial