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