michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "mp4_demuxer/track_run_iterator.h" michael@0: #include "mp4_demuxer/basictypes.h" michael@0: #include "mp4_demuxer/Streams.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: using namespace std; michael@0: michael@0: namespace mp4_demuxer { michael@0: michael@0: static const uint32_t kSampleIsDifferenceSampleFlagMask = 0x10000; michael@0: michael@0: struct SampleInfo { michael@0: int size; michael@0: int duration; michael@0: int cts_offset; michael@0: bool is_keyframe; michael@0: }; michael@0: michael@0: struct TrackRunInfo { michael@0: uint32_t track_id; michael@0: std::vector samples; michael@0: int64_t timescale; michael@0: int64_t start_dts; michael@0: int64_t sample_start_offset; michael@0: michael@0: bool is_audio; michael@0: const AudioSampleEntry* audio_description; michael@0: const VideoSampleEntry* video_description; michael@0: michael@0: int64_t aux_info_start_offset; // Only valid if aux_info_total_size > 0. michael@0: int aux_info_default_size; michael@0: std::vector aux_info_sizes; // Populated if default_size == 0. michael@0: int aux_info_total_size; michael@0: michael@0: TrackRunInfo(); michael@0: ~TrackRunInfo(); michael@0: }; michael@0: michael@0: TrackRunInfo::TrackRunInfo() michael@0: : track_id(0), michael@0: timescale(-1), michael@0: start_dts(-1), michael@0: sample_start_offset(-1), michael@0: is_audio(false), michael@0: aux_info_start_offset(-1), michael@0: aux_info_default_size(-1), michael@0: aux_info_total_size(-1) { michael@0: } michael@0: TrackRunInfo::~TrackRunInfo() {} michael@0: michael@0: Microseconds TimeDeltaFromRational(int64_t numer, int64_t denom) { michael@0: DCHECK_LT((numer > 0 ? numer : -numer), michael@0: kint64max / MicrosecondsPerSecond); michael@0: return MicrosecondsPerSecond * numer / denom; michael@0: } michael@0: michael@0: TrackRunIterator::TrackRunIterator(const Movie* moov) michael@0: : moov_(moov), sample_offset_(0) { michael@0: CHECK(moov); michael@0: } michael@0: michael@0: TrackRunIterator::~TrackRunIterator() {} michael@0: michael@0: static void PopulateSampleInfo(const TrackExtends& trex, michael@0: const TrackFragmentHeader& tfhd, michael@0: const TrackFragmentRun& trun, michael@0: const int64_t edit_list_offset, michael@0: const uint32_t i, michael@0: SampleInfo* sample_info) { michael@0: if (i < trun.sample_sizes.size()) { michael@0: sample_info->size = trun.sample_sizes[i]; michael@0: } else if (tfhd.default_sample_size > 0) { michael@0: sample_info->size = tfhd.default_sample_size; michael@0: } else { michael@0: sample_info->size = trex.default_sample_size; michael@0: } michael@0: michael@0: if (i < trun.sample_durations.size()) { michael@0: sample_info->duration = trun.sample_durations[i]; michael@0: } else if (tfhd.default_sample_duration > 0) { michael@0: sample_info->duration = tfhd.default_sample_duration; michael@0: } else { michael@0: sample_info->duration = trex.default_sample_duration; michael@0: } michael@0: michael@0: if (i < trun.sample_composition_time_offsets.size()) { michael@0: sample_info->cts_offset = trun.sample_composition_time_offsets[i]; michael@0: } else { michael@0: sample_info->cts_offset = 0; michael@0: } michael@0: sample_info->cts_offset += edit_list_offset; michael@0: michael@0: uint32_t flags; michael@0: if (i < trun.sample_flags.size()) { michael@0: flags = trun.sample_flags[i]; michael@0: } else if (tfhd.has_default_sample_flags) { michael@0: flags = tfhd.default_sample_flags; michael@0: } else { michael@0: flags = trex.default_sample_flags; michael@0: } michael@0: sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask); michael@0: } michael@0: michael@0: // In well-structured encrypted media, each track run will be immediately michael@0: // preceded by its auxiliary information; this is the only optimal storage michael@0: // pattern in terms of minimum number of bytes from a serial stream needed to michael@0: // begin playback. It also allows us to optimize caching on memory-constrained michael@0: // architectures, because we can cache the relatively small auxiliary michael@0: // information for an entire run and then discard data from the input stream, michael@0: // instead of retaining the entire 'mdat' box. michael@0: // michael@0: // We optimize for this situation (with no loss of generality) by sorting track michael@0: // runs during iteration in order of their first data offset (either sample data michael@0: // or auxiliary data). michael@0: class CompareMinTrackRunDataOffset { michael@0: public: michael@0: bool operator()(const TrackRunInfo& a, const TrackRunInfo& b) { michael@0: int64_t a_aux = a.aux_info_total_size ? a.aux_info_start_offset : kint64max; michael@0: int64_t b_aux = b.aux_info_total_size ? b.aux_info_start_offset : kint64max; michael@0: michael@0: int64_t a_lesser = std::min(a_aux, a.sample_start_offset); michael@0: int64_t a_greater = std::max(a_aux, a.sample_start_offset); michael@0: int64_t b_lesser = std::min(b_aux, b.sample_start_offset); michael@0: int64_t b_greater = std::max(b_aux, b.sample_start_offset); michael@0: michael@0: if (a_lesser == b_lesser) return a_greater < b_greater; michael@0: return a_lesser < b_lesser; michael@0: } michael@0: }; michael@0: michael@0: bool TrackRunIterator::Init(const MovieFragment& moof) { michael@0: runs_.clear(); michael@0: michael@0: for (size_t i = 0; i < moof.tracks.size(); i++) { michael@0: const TrackFragment& traf = moof.tracks[i]; michael@0: michael@0: const Track* trak = NULL; michael@0: for (size_t t = 0; t < moov_->tracks.size(); t++) { michael@0: if (moov_->tracks[t].header.track_id == traf.header.track_id) michael@0: trak = &moov_->tracks[t]; michael@0: } michael@0: RCHECK(trak); michael@0: michael@0: const TrackExtends* trex = NULL; michael@0: for (size_t t = 0; t < moov_->extends.tracks.size(); t++) { michael@0: if (moov_->extends.tracks[t].track_id == traf.header.track_id) michael@0: trex = &moov_->extends.tracks[t]; michael@0: } michael@0: RCHECK(trex); michael@0: michael@0: const SampleDescription& stsd = michael@0: trak->media.information.sample_table.description; michael@0: if (stsd.type != kAudio && stsd.type != kVideo) { michael@0: DMX_LOG("Skipping unhandled track type\n"); michael@0: continue; michael@0: } michael@0: size_t desc_idx = traf.header.sample_description_index; michael@0: if (!desc_idx) desc_idx = trex->default_sample_description_index; michael@0: RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file michael@0: desc_idx -= 1; michael@0: michael@0: // Process edit list to remove CTS offset introduced in the presence of michael@0: // B-frames (those that contain a single edit with a nonnegative media michael@0: // time). Other uses of edit lists are not supported, as they are michael@0: // both uncommon and better served by higher-level protocols. michael@0: int64_t edit_list_offset = 0; michael@0: const std::vector& edits = trak->edit.list.edits; michael@0: if (!edits.empty()) { michael@0: if (edits.size() > 1) michael@0: DMX_LOG("Multi-entry edit box detected; some components ignored.\n"); michael@0: michael@0: if (edits[0].media_time < 0) { michael@0: DMX_LOG("Empty edit list entry ignored.\n"); michael@0: } else { michael@0: edit_list_offset = -edits[0].media_time; michael@0: } michael@0: } michael@0: michael@0: int64_t run_start_dts = traf.decode_time.decode_time; michael@0: int sample_count_sum = 0; michael@0: for (size_t j = 0; j < traf.runs.size(); j++) { michael@0: const TrackFragmentRun& trun = traf.runs[j]; michael@0: TrackRunInfo tri; michael@0: tri.track_id = traf.header.track_id; michael@0: tri.timescale = trak->media.header.timescale; michael@0: tri.start_dts = run_start_dts; michael@0: tri.sample_start_offset = trun.data_offset; michael@0: michael@0: tri.is_audio = (stsd.type == kAudio); michael@0: if (tri.is_audio) { michael@0: RCHECK(!stsd.audio_entries.empty()); michael@0: if (desc_idx > stsd.audio_entries.size()) michael@0: desc_idx = 0; michael@0: tri.audio_description = &stsd.audio_entries[desc_idx]; michael@0: } else { michael@0: RCHECK(!stsd.video_entries.empty()); michael@0: if (desc_idx > stsd.video_entries.size()) michael@0: desc_idx = 0; michael@0: tri.video_description = &stsd.video_entries[desc_idx]; michael@0: } michael@0: michael@0: // Collect information from the auxiliary_offset entry with the same index michael@0: // in the 'saiz' container as the current run's index in the 'trun' michael@0: // container, if it is present. michael@0: if (traf.auxiliary_offset.offsets.size() > j) { michael@0: // There should be an auxiliary info entry corresponding to each sample michael@0: // in the auxiliary offset entry's corresponding track run. michael@0: RCHECK(traf.auxiliary_size.sample_count >= michael@0: sample_count_sum + trun.sample_count); michael@0: tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j]; michael@0: tri.aux_info_default_size = michael@0: traf.auxiliary_size.default_sample_info_size; michael@0: if (tri.aux_info_default_size == 0) { michael@0: const std::vector& sizes = michael@0: traf.auxiliary_size.sample_info_sizes; michael@0: tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(), michael@0: sizes.begin() + sample_count_sum, michael@0: sizes.begin() + sample_count_sum + trun.sample_count); michael@0: } michael@0: michael@0: // If the default info size is positive, find the total size of the aux michael@0: // info block from it, otherwise sum over the individual sizes of each michael@0: // aux info entry in the aux_offset entry. michael@0: if (tri.aux_info_default_size) { michael@0: tri.aux_info_total_size = michael@0: tri.aux_info_default_size * trun.sample_count; michael@0: } else { michael@0: tri.aux_info_total_size = 0; michael@0: for (size_t k = 0; k < trun.sample_count; k++) { michael@0: tri.aux_info_total_size += tri.aux_info_sizes[k]; michael@0: } michael@0: } michael@0: } else { michael@0: tri.aux_info_start_offset = -1; michael@0: tri.aux_info_total_size = 0; michael@0: } michael@0: michael@0: tri.samples.resize(trun.sample_count); michael@0: for (size_t k = 0; k < trun.sample_count; k++) { michael@0: PopulateSampleInfo(*trex, traf.header, trun, edit_list_offset, michael@0: k, &tri.samples[k]); michael@0: run_start_dts += tri.samples[k].duration; michael@0: } michael@0: runs_.push_back(tri); michael@0: sample_count_sum += trun.sample_count; michael@0: } michael@0: } michael@0: michael@0: std::sort(runs_.begin(), runs_.end(), CompareMinTrackRunDataOffset()); michael@0: run_itr_ = runs_.begin(); michael@0: ResetRun(); michael@0: return true; michael@0: } michael@0: michael@0: void TrackRunIterator::AdvanceRun() { michael@0: ++run_itr_; michael@0: ResetRun(); michael@0: } michael@0: michael@0: void TrackRunIterator::ResetRun() { michael@0: if (!IsRunValid()) return; michael@0: sample_dts_ = run_itr_->start_dts; michael@0: sample_offset_ = run_itr_->sample_start_offset; michael@0: sample_itr_ = run_itr_->samples.begin(); michael@0: cenc_info_.clear(); michael@0: } michael@0: michael@0: void TrackRunIterator::AdvanceSample() { michael@0: DCHECK(IsSampleValid()); michael@0: sample_dts_ += sample_itr_->duration; michael@0: sample_offset_ += sample_itr_->size; michael@0: ++sample_itr_; michael@0: } michael@0: michael@0: // This implementation only indicates a need for caching if CENC auxiliary michael@0: // info is available in the stream. michael@0: bool TrackRunIterator::AuxInfoNeedsToBeCached() { michael@0: DCHECK(IsRunValid()); michael@0: return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0; michael@0: } michael@0: michael@0: // This implementation currently only caches CENC auxiliary info. michael@0: bool TrackRunIterator::CacheAuxInfo(Stream* stream, int64_t moof_offset) { michael@0: RCHECK(AuxInfoNeedsToBeCached()); michael@0: michael@0: int64_t offset = aux_info_offset() + moof_offset; michael@0: if (stream->Length() - offset < aux_info_size()) { michael@0: return false; michael@0: } michael@0: michael@0: assert(run_itr_ == runs_.begin()); michael@0: cenc_info_.resize(run_itr_->samples.size()); michael@0: int64_t pos = 0; michael@0: for (size_t i = 0; i < run_itr_->samples.size(); i++) { michael@0: int info_size = run_itr_->aux_info_default_size; michael@0: if (!info_size) michael@0: info_size = run_itr_->aux_info_sizes[i]; michael@0: michael@0: StreamReader reader(stream, offset + pos, info_size); michael@0: RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader)); michael@0: pos += info_size; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool TrackRunIterator::IsRunValid() const { michael@0: return run_itr_ != runs_.end(); michael@0: } michael@0: michael@0: bool TrackRunIterator::IsSampleValid() const { michael@0: return IsRunValid() && (sample_itr_ != run_itr_->samples.end()); michael@0: } michael@0: michael@0: // Because tracks are in sorted order and auxiliary information is cached when michael@0: // returning samples, it is guaranteed that no data will be required before the michael@0: // lesser of the minimum data offset of this track and the next in sequence. michael@0: // (The stronger condition - that no data is required before the minimum data michael@0: // offset of this track alone - is not guaranteed, because the BMFF spec does michael@0: // not have any inter-run ordering restrictions.) michael@0: int64_t TrackRunIterator::GetMaxClearOffset() { michael@0: int64_t offset = kint64max; michael@0: michael@0: if (IsSampleValid()) { michael@0: offset = std::min(offset, sample_offset_); michael@0: if (AuxInfoNeedsToBeCached()) michael@0: offset = std::min(offset, aux_info_offset()); michael@0: } michael@0: if (run_itr_ != runs_.end()) { michael@0: std::vector::const_iterator next_run = run_itr_ + 1; michael@0: if (next_run != runs_.end()) { michael@0: offset = std::min(offset, next_run->sample_start_offset); michael@0: if (next_run->aux_info_total_size) michael@0: offset = std::min(offset, next_run->aux_info_start_offset); michael@0: } michael@0: } michael@0: if (offset == kint64max) return 0; michael@0: return offset; michael@0: } michael@0: michael@0: Microseconds TrackRunIterator::GetMinDecodeTimestamp() { michael@0: Microseconds dts = -1; michael@0: for (size_t i = 0; i < runs_.size(); i++) { michael@0: dts = std::min(dts, MicrosecondsFromRational(runs_[i].start_dts, michael@0: runs_[i].timescale)); michael@0: } michael@0: return dts; michael@0: } michael@0: michael@0: uint32_t TrackRunIterator::track_id() const { michael@0: DCHECK(IsRunValid()); michael@0: return run_itr_->track_id; michael@0: } michael@0: michael@0: bool TrackRunIterator::is_encrypted() const { michael@0: DCHECK(IsRunValid()); michael@0: return track_encryption().is_encrypted; michael@0: } michael@0: michael@0: int64_t TrackRunIterator::aux_info_offset() const { michael@0: return run_itr_->aux_info_start_offset; michael@0: } michael@0: michael@0: int TrackRunIterator::aux_info_size() const { michael@0: return run_itr_->aux_info_total_size; michael@0: } michael@0: michael@0: bool TrackRunIterator::is_audio() const { michael@0: DCHECK(IsRunValid()); michael@0: return run_itr_->is_audio; michael@0: } michael@0: michael@0: const AudioSampleEntry& TrackRunIterator::audio_description() const { michael@0: DCHECK(is_audio()); michael@0: DCHECK(run_itr_->audio_description); michael@0: return *run_itr_->audio_description; michael@0: } michael@0: michael@0: const VideoSampleEntry& TrackRunIterator::video_description() const { michael@0: DCHECK(!is_audio()); michael@0: DCHECK(run_itr_->video_description); michael@0: return *run_itr_->video_description; michael@0: } michael@0: michael@0: int64_t TrackRunIterator::sample_offset() const { michael@0: DCHECK(IsSampleValid()); michael@0: return sample_offset_; michael@0: } michael@0: michael@0: int TrackRunIterator::sample_size() const { michael@0: DCHECK(IsSampleValid()); michael@0: return sample_itr_->size; michael@0: } michael@0: michael@0: Microseconds TrackRunIterator::dts() const { michael@0: DCHECK(IsSampleValid()); michael@0: return MicrosecondsFromRational(sample_dts_, run_itr_->timescale); michael@0: } michael@0: michael@0: Microseconds TrackRunIterator::cts() const { michael@0: DCHECK(IsSampleValid()); michael@0: return MicrosecondsFromRational(sample_dts_ + sample_itr_->cts_offset, michael@0: run_itr_->timescale); michael@0: } michael@0: michael@0: Microseconds TrackRunIterator::duration() const { michael@0: DCHECK(IsSampleValid()); michael@0: return MicrosecondsFromRational(sample_itr_->duration, run_itr_->timescale); michael@0: } michael@0: michael@0: bool TrackRunIterator::is_keyframe() const { michael@0: DCHECK(IsSampleValid()); michael@0: return sample_itr_->is_keyframe; michael@0: } michael@0: michael@0: const TrackEncryption& TrackRunIterator::track_encryption() const { michael@0: if (is_audio()) michael@0: return audio_description().sinf.info.track_encryption; michael@0: return video_description().sinf.info.track_encryption; michael@0: } michael@0: michael@0: void TrackRunIterator::GetDecryptConfig(nsAutoPtr& config) { michael@0: size_t sample_idx = sample_itr_ - run_itr_->samples.begin(); michael@0: DCHECK(sample_idx < cenc_info_.size()); michael@0: const FrameCENCInfo& cenc_info = cenc_info_[sample_idx]; michael@0: DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached()); michael@0: michael@0: if (!cenc_info.subsamples.empty() && michael@0: (cenc_info.GetTotalSizeOfSubsamples() != michael@0: static_cast(sample_size()))) { michael@0: DMX_LOG("Incorrect CENC subsample size.\n"); michael@0: return; michael@0: } michael@0: michael@0: const std::vector& kid = track_encryption().default_kid; michael@0: config = new DecryptConfig( michael@0: std::string(reinterpret_cast(&kid[0]), kid.size()), michael@0: std::string(reinterpret_cast(cenc_info.iv), michael@0: arraysize(cenc_info.iv)), michael@0: 0, // No offset to start of media data in MP4 using CENC. michael@0: cenc_info.subsamples); michael@0: } michael@0: michael@0: } // namespace mp4_demuxer