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 13 namespace { 14 static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; 15 } 16 17 namespace media { 18 namespace mp4 { 19 20 struct SampleInfo { 21 int size; 22 int duration; 23 int cts_offset; 24 bool is_keyframe; 25 }; 26 27 struct 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 47 TrackRunInfo::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 } 57 TrackRunInfo::~TrackRunInfo() {} 58 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); 64 } 65 66 TrackRunIterator::TrackRunIterator(const Movie* moov, 67 const LogCB& log_cb) 68 : moov_(moov), log_cb_(log_cb), sample_offset_(0) { 69 CHECK(moov); 70 } 71 72 TrackRunIterator::~TrackRunIterator() {} 73 74 static 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). 142 class 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 158 bool 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 283 void TrackRunIterator::AdvanceRun() { 284 ++run_itr_; 285 ResetRun(); 286 } 287 288 void 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 296 void 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. 305 bool 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. 311 bool 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 329 bool TrackRunIterator::IsRunValid() const { 330 return run_itr_ != runs_.end(); 331 } 332 333 bool 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.) 343 int64 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 363 uint32 TrackRunIterator::track_id() const { 364 DCHECK(IsRunValid()); 365 return run_itr_->track_id; 366 } 367 368 bool TrackRunIterator::is_encrypted() const { 369 DCHECK(IsRunValid()); 370 return track_encryption().is_encrypted; 371 } 372 373 int64 TrackRunIterator::aux_info_offset() const { 374 return run_itr_->aux_info_start_offset; 375 } 376 377 int TrackRunIterator::aux_info_size() const { 378 return run_itr_->aux_info_total_size; 379 } 380 381 bool TrackRunIterator::is_audio() const { 382 DCHECK(IsRunValid()); 383 return run_itr_->is_audio; 384 } 385 386 const AudioSampleEntry& TrackRunIterator::audio_description() const { 387 DCHECK(is_audio()); 388 DCHECK(run_itr_->audio_description); 389 return *run_itr_->audio_description; 390 } 391 392 const VideoSampleEntry& TrackRunIterator::video_description() const { 393 DCHECK(!is_audio()); 394 DCHECK(run_itr_->video_description); 395 return *run_itr_->video_description; 396 } 397 398 int64 TrackRunIterator::sample_offset() const { 399 DCHECK(IsSampleValid()); 400 return sample_offset_; 401 } 402 403 int TrackRunIterator::sample_size() const { 404 DCHECK(IsSampleValid()); 405 return sample_itr_->size; 406 } 407 408 TimeDelta TrackRunIterator::dts() const { 409 DCHECK(IsSampleValid()); 410 return TimeDeltaFromRational(sample_dts_, run_itr_->timescale); 411 } 412 413 TimeDelta TrackRunIterator::cts() const { 414 DCHECK(IsSampleValid()); 415 return TimeDeltaFromRational(sample_dts_ + sample_itr_->cts_offset, 416 run_itr_->timescale); 417 } 418 419 TimeDelta TrackRunIterator::duration() const { 420 DCHECK(IsSampleValid()); 421 return TimeDeltaFromRational(sample_itr_->duration, run_itr_->timescale); 422 } 423 424 bool TrackRunIterator::is_keyframe() const { 425 DCHECK(IsSampleValid()); 426 return sample_itr_->is_keyframe; 427 } 428 429 const 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 435 scoped_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 size_t total_size = 0; 442 if (!cenc_info.subsamples.empty() && 443 (!cenc_info.GetTotalSizeOfSubsamples(&total_size) || 444 total_size != static_cast<size_t>(sample_size()))) { 445 MEDIA_LOG(log_cb_) << "Incorrect CENC subsample size."; 446 return scoped_ptr<DecryptConfig>(); 447 } 448 449 const std::vector<uint8>& kid = track_encryption().default_kid; 450 return scoped_ptr<DecryptConfig>(new DecryptConfig( 451 std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()), 452 std::string(reinterpret_cast<const char*>(cenc_info.iv), 453 arraysize(cenc_info.iv)), 454 0, // No offset to start of media data in MP4 using CENC. 455 cenc_info.subsamples)); 456 } 457 458 } // namespace mp4 459 } // namespace media 460