track_run_iterator.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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. 4 5#include "media/mp4/track_run_iterator.h" 6 7#include <algorithm> 8 9#include "media/base/buffers.h" 10#include "media/base/stream_parser_buffer.h" 11#include "media/mp4/rcheck.h" 12 13namespace { 14static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; 15} 16 17namespace media { 18namespace mp4 { 19 20struct SampleInfo { 21 int size; 22 int duration; 23 int cts_offset; 24 bool is_keyframe; 25}; 26 27struct TrackRunInfo { 28 uint32 track_id; 29 std::vector<SampleInfo> samples; 30 int64 timescale; 31 int64 start_dts; 32 int64 sample_start_offset; 33 34 bool is_audio; 35 const AudioSampleEntry* audio_description; 36 const VideoSampleEntry* video_description; 37 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; 42 43 TrackRunInfo(); 44 ~TrackRunInfo(); 45}; 46 47TrackRunInfo::TrackRunInfo() 48 : track_id(0), 49 timescale(-1), 50 start_dts(-1), 51 sample_start_offset(-1), 52 is_audio(false), 53 aux_info_start_offset(-1), 54 aux_info_default_size(-1), 55 aux_info_total_size(-1) { 56} 57TrackRunInfo::~TrackRunInfo() {} 58 59TimeDelta 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); 64} 65 66TrackRunIterator::TrackRunIterator(const Movie* moov, 67 const LogCB& log_cb) 68 : moov_(moov), log_cb_(log_cb), sample_offset_(0) { 69 CHECK(moov); 70} 71 72TrackRunIterator::~TrackRunIterator() {} 73 74static void PopulateSampleInfo(const TrackExtends& trex, 75 const TrackFragmentHeader& tfhd, 76 const TrackFragmentRun& trun, 77 const int64 edit_list_offset, 78 const uint32 i, 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; 85 } else { 86 sample_info->size = trex.default_sample_size; 87 } 88 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; 93 } else { 94 sample_info->duration = trex.default_sample_duration; 95 } 96 97 if (i < trun.sample_composition_time_offsets.size()) { 98 sample_info->cts_offset = trun.sample_composition_time_offsets[i]; 99 } else { 100 sample_info->cts_offset = 0; 101 } 102 sample_info->cts_offset += edit_list_offset; 103 104 uint32 flags; 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; 109 } else { 110 flags = trex.default_sample_flags; 111 } 112 113 switch (sample_depends_on) { 114 case kSampleDependsOnUnknown: 115 sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask); 116 break; 117 118 case kSampleDependsOnOthers: 119 sample_info->is_keyframe = false; 120 break; 121 122 case kSampleDependsOnNoOther: 123 sample_info->is_keyframe = true; 124 break; 125 126 case kSampleDependsOnReserved: 127 CHECK(false); 128 } 129} 130 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. 138// 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). 142class CompareMinTrackRunDataOffset { 143 public: 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; 147 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); 152 153 if (a_lesser == b_lesser) return a_greater < b_greater; 154 return a_lesser < b_lesser; 155 } 156}; 157 158bool TrackRunIterator::Init(const MovieFragment& moof) { 159 runs_.clear(); 160 161 for (size_t i = 0; i < moof.tracks.size(); i++) { 162 const TrackFragment& traf = moof.tracks[i]; 163 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]; 168 } 169 RCHECK(trak); 170 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]; 175 } 176 RCHECK(trex); 177 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"; 182 continue; 183 } 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 187 desc_idx -= 1; 188 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."; 198 199 if (edits[0].media_time < 0) { 200 DVLOG(1) << "Empty edit list entry ignored."; 201 } else { 202 edit_list_offset = -edits[0].media_time; 203 } 204 } 205 206 int64 run_start_dts = traf.decode_time.decode_time; 207 int sample_count_sum = 0; 208 209 for (size_t j = 0; j < traf.runs.size(); j++) { 210 const TrackFragmentRun& trun = traf.runs[j]; 211 TrackRunInfo tri; 212 tri.track_id = traf.header.track_id; 213 tri.timescale = trak->media.header.timescale; 214 tri.start_dts = run_start_dts; 215 tri.sample_start_offset = trun.data_offset; 216 217 tri.is_audio = (stsd.type == kAudio); 218 if (tri.is_audio) { 219 RCHECK(!stsd.audio_entries.empty()); 220 if (desc_idx > stsd.audio_entries.size()) 221 desc_idx = 0; 222 tri.audio_description = &stsd.audio_entries[desc_idx]; 223 } else { 224 RCHECK(!stsd.video_entries.empty()); 225 if (desc_idx > stsd.video_entries.size()) 226 desc_idx = 0; 227 tri.video_description = &stsd.video_entries[desc_idx]; 228 } 229 230 // Collect information from the auxiliary_offset entry with the same index 231 // in the 'saiz' container as the current run's index in the 'trun' 232 // container, if it is present. 233 if (traf.auxiliary_offset.offsets.size() > j) { 234 // There should be an auxiliary info entry corresponding to each sample 235 // in the auxiliary offset entry's corresponding track run. 236 RCHECK(traf.auxiliary_size.sample_count >= 237 sample_count_sum + trun.sample_count); 238 tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j]; 239 tri.aux_info_default_size = 240 traf.auxiliary_size.default_sample_info_size; 241 if (tri.aux_info_default_size == 0) { 242 const std::vector<uint8>& sizes = 243 traf.auxiliary_size.sample_info_sizes; 244 tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(), 245 sizes.begin() + sample_count_sum, 246 sizes.begin() + sample_count_sum + trun.sample_count); 247 } 248 249 // If the default info size is positive, find the total size of the aux 250 // info block from it, otherwise sum over the individual sizes of each 251 // aux info entry in the aux_offset entry. 252 if (tri.aux_info_default_size) { 253 tri.aux_info_total_size = 254 tri.aux_info_default_size * trun.sample_count; 255 } else { 256 tri.aux_info_total_size = 0; 257 for (size_t k = 0; k < trun.sample_count; k++) { 258 tri.aux_info_total_size += tri.aux_info_sizes[k]; 259 } 260 } 261 } else { 262 tri.aux_info_start_offset = -1; 263 tri.aux_info_total_size = 0; 264 } 265 266 tri.samples.resize(trun.sample_count); 267 for (size_t k = 0; k < trun.sample_count; k++) { 268 PopulateSampleInfo(*trex, traf.header, trun, edit_list_offset, 269 k, &tri.samples[k], traf.sdtp.sample_depends_on(k)); 270 run_start_dts += tri.samples[k].duration; 271 } 272 runs_.push_back(tri); 273 sample_count_sum += trun.sample_count; 274 } 275 } 276 277 std::sort(runs_.begin(), runs_.end(), CompareMinTrackRunDataOffset()); 278 run_itr_ = runs_.begin(); 279 ResetRun(); 280 return true; 281} 282 283void TrackRunIterator::AdvanceRun() { 284 ++run_itr_; 285 ResetRun(); 286} 287 288void TrackRunIterator::ResetRun() { 289 if (!IsRunValid()) return; 290 sample_dts_ = run_itr_->start_dts; 291 sample_offset_ = run_itr_->sample_start_offset; 292 sample_itr_ = run_itr_->samples.begin(); 293 cenc_info_.clear(); 294} 295 296void TrackRunIterator::AdvanceSample() { 297 DCHECK(IsSampleValid()); 298 sample_dts_ += sample_itr_->duration; 299 sample_offset_ += sample_itr_->size; 300 ++sample_itr_; 301} 302 303// This implementation only indicates a need for caching if CENC auxiliary 304// info is available in the stream. 305bool TrackRunIterator::AuxInfoNeedsToBeCached() { 306 DCHECK(IsRunValid()); 307 return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0; 308} 309 310// This implementation currently only caches CENC auxiliary info. 311bool TrackRunIterator::CacheAuxInfo(const uint8* buf, int buf_size) { 312 RCHECK(AuxInfoNeedsToBeCached() && buf_size >= aux_info_size()); 313 314 cenc_info_.resize(run_itr_->samples.size()); 315 int64 pos = 0; 316 for (size_t i = 0; i < run_itr_->samples.size(); i++) { 317 int info_size = run_itr_->aux_info_default_size; 318 if (!info_size) 319 info_size = run_itr_->aux_info_sizes[i]; 320 321 BufferReader reader(buf + pos, info_size); 322 RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader)); 323 pos += info_size; 324 } 325 326 return true; 327} 328 329bool TrackRunIterator::IsRunValid() const { 330 return run_itr_ != runs_.end(); 331} 332 333bool TrackRunIterator::IsSampleValid() const { 334 return IsRunValid() && (sample_itr_ != run_itr_->samples.end()); 335} 336 337// Because tracks are in sorted order and auxiliary information is cached when 338// returning samples, it is guaranteed that no data will be required before the 339// lesser of the minimum data offset of this track and the next in sequence. 340// (The stronger condition - that no data is required before the minimum data 341// offset of this track alone - is not guaranteed, because the BMFF spec does 342// not have any inter-run ordering restrictions.) 343int64 TrackRunIterator::GetMaxClearOffset() { 344 int64 offset = kint64max; 345 346 if (IsSampleValid()) { 347 offset = std::min(offset, sample_offset_); 348 if (AuxInfoNeedsToBeCached()) 349 offset = std::min(offset, aux_info_offset()); 350 } 351 if (run_itr_ != runs_.end()) { 352 std::vector<TrackRunInfo>::const_iterator next_run = run_itr_ + 1; 353 if (next_run != runs_.end()) { 354 offset = std::min(offset, next_run->sample_start_offset); 355 if (next_run->aux_info_total_size) 356 offset = std::min(offset, next_run->aux_info_start_offset); 357 } 358 } 359 if (offset == kint64max) return 0; 360 return offset; 361} 362 363uint32 TrackRunIterator::track_id() const { 364 DCHECK(IsRunValid()); 365 return run_itr_->track_id; 366} 367 368bool TrackRunIterator::is_encrypted() const { 369 DCHECK(IsRunValid()); 370 return track_encryption().is_encrypted; 371} 372 373int64 TrackRunIterator::aux_info_offset() const { 374 return run_itr_->aux_info_start_offset; 375} 376 377int TrackRunIterator::aux_info_size() const { 378 return run_itr_->aux_info_total_size; 379} 380 381bool TrackRunIterator::is_audio() const { 382 DCHECK(IsRunValid()); 383 return run_itr_->is_audio; 384} 385 386const AudioSampleEntry& TrackRunIterator::audio_description() const { 387 DCHECK(is_audio()); 388 DCHECK(run_itr_->audio_description); 389 return *run_itr_->audio_description; 390} 391 392const VideoSampleEntry& TrackRunIterator::video_description() const { 393 DCHECK(!is_audio()); 394 DCHECK(run_itr_->video_description); 395 return *run_itr_->video_description; 396} 397 398int64 TrackRunIterator::sample_offset() const { 399 DCHECK(IsSampleValid()); 400 return sample_offset_; 401} 402 403int TrackRunIterator::sample_size() const { 404 DCHECK(IsSampleValid()); 405 return sample_itr_->size; 406} 407 408TimeDelta TrackRunIterator::dts() const { 409 DCHECK(IsSampleValid()); 410 return TimeDeltaFromRational(sample_dts_, run_itr_->timescale); 411} 412 413TimeDelta TrackRunIterator::cts() const { 414 DCHECK(IsSampleValid()); 415 return TimeDeltaFromRational(sample_dts_ + sample_itr_->cts_offset, 416 run_itr_->timescale); 417} 418 419TimeDelta TrackRunIterator::duration() const { 420 DCHECK(IsSampleValid()); 421 return TimeDeltaFromRational(sample_itr_->duration, run_itr_->timescale); 422} 423 424bool TrackRunIterator::is_keyframe() const { 425 DCHECK(IsSampleValid()); 426 return sample_itr_->is_keyframe; 427} 428 429const TrackEncryption& TrackRunIterator::track_encryption() const { 430 if (is_audio()) 431 return audio_description().sinf.info.track_encryption; 432 return video_description().sinf.info.track_encryption; 433} 434 435scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() { 436 size_t sample_idx = sample_itr_ - run_itr_->samples.begin(); 437 DCHECK(sample_idx < cenc_info_.size()); 438 const FrameCENCInfo& cenc_info = cenc_info_[sample_idx]; 439 DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached()); 440 441 if (!cenc_info.subsamples.empty() && 442 (cenc_info.GetTotalSizeOfSubsamples() != 443 static_cast<size_t>(sample_size()))) { 444 MEDIA_LOG(log_cb_) << "Incorrect CENC subsample size."; 445 return scoped_ptr<DecryptConfig>(); 446 } 447 448 const std::vector<uint8>& kid = track_encryption().default_kid; 449 return scoped_ptr<DecryptConfig>(new DecryptConfig( 450 std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()), 451 std::string(reinterpret_cast<const char*>(cenc_info.iv), 452 arraysize(cenc_info.iv)), 453 0, // No offset to start of media data in MP4 using CENC. 454 cenc_info.subsamples)); 455} 456 457} // namespace mp4 458} // namespace media 459