| /* |
| * Copyright 2012 Google Inc. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "media/filters/shell_mp4_map.h" |
| |
| #include "base/stringprintf.h" |
| #include "media/base/endian_util.h" |
| #include "media/filters/shell_mp4_parser.h" |
| |
| namespace media { |
| |
| // ==== TableCache ============================================================= |
| |
| ShellMP4Map::TableCache::TableCache(uint64 table_offset, |
| uint32 entry_count, |
| uint32 entry_size, |
| uint32 cache_size_entries, |
| scoped_refptr<ShellDataSourceReader> reader) |
| : entry_size_(entry_size), |
| entry_count_(entry_count), |
| cache_size_entries_(cache_size_entries), |
| table_offset_(table_offset), |
| reader_(reader), |
| cache_first_entry_number_(-1), |
| cache_entry_count_(0) {} |
| |
| uint8* ShellMP4Map::TableCache::GetBytesAtEntry(uint32 entry_number) { |
| // don't fetch the unfetchable |
| if (entry_number >= entry_count_) { |
| return NULL; |
| } |
| // this query within valid range for the current cache table? |
| if (entry_number < cache_first_entry_number_ || |
| entry_number >= cache_first_entry_number_ + cache_entry_count_) { |
| // Calculate first entry in table keeping cache size alignment in table. |
| // Always cache one more entry as in stss we need to use the first entry |
| // of the next cache slot as an upper bound. |
| cache_entry_count_ = cache_size_entries_ + 1; |
| cache_first_entry_number_ = |
| (entry_number / cache_size_entries_) * cache_size_entries_; |
| // see if we have exceeded our table bounds |
| if (cache_first_entry_number_ + cache_entry_count_ > entry_count_) { |
| cache_entry_count_ = entry_count_ - cache_first_entry_number_; |
| } |
| // drop old data to allow ShellBufferFactory to defrag |
| cache_ = NULL; |
| int bytes_to_read = cache_entry_count_ * entry_size_; |
| cache_ = ShellBufferFactory::Instance()->AllocateArray(bytes_to_read); |
| if (!cache_ || !cache_->Get()) { |
| cache_entry_count_ = 0; |
| return NULL; |
| } |
| uint64 file_offset = |
| table_offset_ + (cache_first_entry_number_ * entry_size_); |
| int bytes_read = |
| reader_->BlockingRead(file_offset, bytes_to_read, cache_->Get()); |
| if (bytes_read < bytes_to_read) { |
| cache_entry_count_ = 0; |
| return NULL; |
| } |
| } |
| // cache is assumed to be valid and to contain the entry from here on |
| DCHECK(cache_->Get()); |
| DCHECK_GE(entry_number, cache_first_entry_number_); |
| DCHECK_LT(entry_number, cache_first_entry_number_ + cache_entry_count_); |
| |
| uint32 cache_offset = entry_number - cache_first_entry_number_; |
| return cache_->Get() + (cache_offset * entry_size_); |
| } |
| |
| bool ShellMP4Map::TableCache::ReadU32Entry(uint32 entry_number, uint32* entry) { |
| if (uint8* data = GetBytesAtEntry(entry_number)) { |
| *entry = endian_util::load_uint32_big_endian(data); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ShellMP4Map::TableCache::ReadU32PairEntry(uint32 entry_number, |
| uint32* first, |
| uint32* second) { |
| if (uint8* data = GetBytesAtEntry(entry_number)) { |
| if (first) |
| *first = endian_util::load_uint32_big_endian(data); |
| if (second) |
| *second = endian_util::load_uint32_big_endian(data + 4); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ShellMP4Map::TableCache::ReadU32EntryIntoU64(uint32 entry_number, |
| uint64* entry) { |
| if (uint8* data = GetBytesAtEntry(entry_number)) { |
| *entry = endian_util::load_uint32_big_endian(data); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ShellMP4Map::TableCache::ReadU64Entry(uint32 entry_number, uint64* entry) { |
| if (uint8* data = GetBytesAtEntry(entry_number)) { |
| *entry = endian_util::load_uint64_big_endian(data); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // ==== ShellMP4Map ============================================================ |
| |
| // atom | name | size | description, (*) means optional table |
| // -----+-----------------------+------+---------------------------------------- |
| // co64 | chunk offset (64-bit) | 8 | per-chunk list of chunk file offsets |
| // ctts | composition offset | 8 | (*) run-length sample number to cts |
| // stco | chunk offset (32-bit) | 4 | per-chunk list of chunk file offsets |
| // stsc | sample-to-chunk | 12 | chunk number to samples per chunk |
| // stss | sync sample | 4 | (*) list of keyframe sample numbers |
| // stts | time-to-sample | 8 | run-length sample number to duration |
| // stsz | sample size | 4 | per-sample list of sample sizes |
| |
| ShellMP4Map::ShellMP4Map(scoped_refptr<ShellDataSourceReader> reader) |
| : reader_(reader), |
| current_chunk_sample_(0), |
| next_chunk_sample_(0), |
| current_chunk_offset_(0), |
| highest_valid_sample_number_(UINT32_MAX), |
| ctts_first_sample_(0), |
| ctts_sample_offset_(0), |
| ctts_next_first_sample_(0), |
| ctts_table_index_(0), |
| stsc_first_chunk_(0), |
| stsc_first_chunk_sample_(0), |
| stsc_samples_per_chunk_(0), |
| stsc_next_first_chunk_(0), |
| stsc_next_first_chunk_sample_(0), |
| stsc_table_index_(0), |
| stss_last_keyframe_(0), |
| stss_next_keyframe_(0), |
| stss_table_index_(0), |
| stts_first_sample_(0), |
| stts_first_sample_time_(0), |
| stts_sample_duration_(0), |
| stts_next_first_sample_(0), |
| stts_next_first_sample_time_(0), |
| stts_table_index_(0), |
| stsz_default_size_(0) {} |
| |
| bool ShellMP4Map::IsComplete() { |
| // all required table pointers must be valid for map to function |
| return (co64_ || stco_) && stsc_ && stts_ && (stsz_ || stsz_default_size_); |
| } |
| |
| // The sample size is a lookup in the stsz table, which is indexed per sample |
| // number. |
| bool ShellMP4Map::GetSize(uint32 sample_number, uint32& size_out) { |
| DCHECK(stsz_ || stsz_default_size_); |
| |
| if (sample_number > highest_valid_sample_number_) { |
| return false; |
| } |
| |
| if (stsz_default_size_) { |
| size_out = stsz_default_size_; |
| return true; |
| } |
| |
| return stsz_->ReadU32Entry(sample_number, &size_out); |
| } |
| |
| // We first must integrate the stsc table to find the chunk number that the |
| // sample resides in, and the first sample number in that chunk. We look up that |
| // chunk offset from the stco or co64, which are indexed by chunk number. We |
| // then use the stsz to sum samples to the byte offset with that chunk. The sum |
| // of the chunk offset and the byte offset within the chunk is the offset of |
| // the sample. |
| bool ShellMP4Map::GetOffset(uint32 sample_number, uint64& offset_out) { |
| DCHECK(stsc_); |
| DCHECK(stco_ || co64_); |
| DCHECK(stsz_ || stsz_default_size_); |
| |
| if (sample_number > highest_valid_sample_number_) { |
| return false; |
| } |
| |
| // check for sequential access of sample numbers within the same chunk |
| if (sample_number < current_chunk_sample_ || |
| sample_number >= next_chunk_sample_) { |
| // integrate through stsc until we find the chunk range containing sample |
| if (!stsc_AdvanceToSample(sample_number)) { |
| return false; |
| } |
| // make sure stsc advance did its job correctly |
| DCHECK_GE(sample_number, stsc_first_chunk_sample_); |
| |
| // calculate chunk number based on chunk sample size for this range |
| uint32 sample_offset = sample_number - stsc_first_chunk_sample_; |
| uint32 chunk_range_offset = sample_offset / stsc_samples_per_chunk_; |
| uint32 chunk_number = stsc_first_chunk_ + chunk_range_offset; |
| // should be within the range of chunks with this sample size |
| DCHECK_LT(chunk_number, stsc_next_first_chunk_); |
| // update first sample number contained within this chunk |
| current_chunk_sample_ = stsc_first_chunk_sample_ + |
| (chunk_range_offset * stsc_samples_per_chunk_); |
| // update first sample number of next chunk |
| next_chunk_sample_ = current_chunk_sample_ + stsc_samples_per_chunk_; |
| // find offset of this chunk within the file from co64/stco |
| if (co64_) { |
| if (!co64_->ReadU64Entry(chunk_number, ¤t_chunk_offset_)) |
| return false; |
| } else if (!stco_->ReadU32EntryIntoU64(chunk_number, |
| ¤t_chunk_offset_)) { |
| return false; |
| } |
| } |
| |
| // at this point we should have sample_number within the range of our chunk |
| // offset summation saved state |
| DCHECK_LE(current_chunk_sample_, sample_number); |
| DCHECK_LT(sample_number, next_chunk_sample_); |
| |
| if (stsz_default_size_ > 0) { |
| current_chunk_offset_ += |
| (sample_number - current_chunk_sample_) * stsz_default_size_; |
| current_chunk_sample_ = sample_number; |
| } else { |
| // sum sample sizes within chunk to get to byte offset of sample |
| while (current_chunk_sample_ < sample_number) { |
| uint32 sample_size = 0; |
| if (!GetSize(current_chunk_sample_, sample_size)) { |
| return false; |
| } |
| current_chunk_offset_ += sample_size; |
| current_chunk_sample_++; |
| } |
| } |
| |
| offset_out = current_chunk_offset_; |
| return true; |
| } |
| |
| // Given a current sample number we integrate through the stts to find the |
| // duration of the current sample, and at the same time integrate through the |
| // durations to find the dts of that sample number. We then integrate sample |
| // numbers through the ctts to find the composition time offset, which we add to |
| // the dts to return the pts. |
| bool ShellMP4Map::GetTimestamp(uint32 sample_number, uint64& timestamp_out) { |
| if (sample_number > highest_valid_sample_number_) { |
| return false; |
| } |
| |
| if (!stts_AdvanceToSample(sample_number)) { |
| return false; |
| } |
| DCHECK_LT(sample_number, stts_next_first_sample_); |
| DCHECK_GE(sample_number, stts_first_sample_); |
| uint64 dts = stts_first_sample_time_ + |
| (sample_number - stts_first_sample_) * stts_sample_duration_; |
| if (ctts_) { |
| if (!ctts_AdvanceToSample(sample_number)) { |
| return false; |
| } |
| DCHECK_LT(sample_number, ctts_next_first_sample_); |
| DCHECK_GE(sample_number, ctts_first_sample_); |
| } |
| timestamp_out = dts + ctts_sample_offset_; |
| return true; |
| } |
| |
| // Sum through the stts to find the duration of the given sample_number. |
| bool ShellMP4Map::GetDuration(uint32 sample_number, uint32& duration_out) { |
| if (sample_number > highest_valid_sample_number_) { |
| return false; |
| } |
| |
| if (!stts_AdvanceToSample(sample_number)) { |
| return false; |
| } |
| DCHECK_LT(sample_number, stts_next_first_sample_); |
| DCHECK_GE(sample_number, stts_first_sample_); |
| duration_out = stts_sample_duration_; |
| return true; |
| } |
| |
| bool ShellMP4Map::GetIsKeyframe(uint32 sample_number, bool& is_keyframe_out) { |
| if (sample_number > highest_valid_sample_number_) { |
| return false; |
| } |
| |
| // no stts means every frame is a keyframe |
| if (!stss_) { |
| is_keyframe_out = true; |
| return true; |
| } |
| |
| // check for keyframe match on either range value |
| if (sample_number == stss_next_keyframe_) { |
| is_keyframe_out = true; |
| return stss_AdvanceStep(); |
| } else if (sample_number == stss_last_keyframe_) { |
| is_keyframe_out = true; |
| return true; |
| } |
| |
| // this could be for a much earlier sample number, check if we are within |
| // current range of sample numbers |
| if (sample_number < stss_last_keyframe_ || |
| sample_number > stss_next_keyframe_) { |
| // search for containing entry |
| if (!stss_FindNearestKeyframe(sample_number)) { |
| return false; |
| } |
| } |
| // sample number must be in range of keyframe states |
| DCHECK_GE(sample_number, stss_last_keyframe_); |
| DCHECK_LT(sample_number, stss_next_keyframe_); |
| // stss_FindNearestKeyframe returns exact matches to |
| // sample_number in the stss_last_keyframe_ variable, so |
| // we check that for equality |
| is_keyframe_out = (sample_number == stss_last_keyframe_); |
| |
| return true; |
| } |
| |
| bool ShellMP4Map::IsEOS(uint32 sample_number) { |
| return (sample_number > highest_valid_sample_number_); |
| } |
| |
| // First look up the sample number for the provided timestamp by integrating |
| // timestamps through the stts. Then do a binary search on the stss to find the |
| // keyframe nearest that sample number. |
| bool ShellMP4Map::GetKeyframe(uint64 timestamp, uint32& sample_out) { |
| // Advance stts to the provided timestamp range |
| if (!stts_AdvanceToTime(timestamp)) { |
| return false; |
| } |
| // ensure we got the correct sample duration range |
| DCHECK_LT(timestamp, stts_next_first_sample_time_); |
| DCHECK_GE(timestamp, stts_first_sample_time_); |
| // calculate sample number containing this timestamp |
| uint64 time_offset_within_range = timestamp - stts_first_sample_time_; |
| uint32 sample_number = |
| stts_first_sample_ + (time_offset_within_range / stts_sample_duration_); |
| |
| // TODO: ctts? |
| |
| // binary search on stts to find nearest keyframe beneath this sample number |
| if (stss_) { |
| if (!stss_FindNearestKeyframe(sample_number)) { |
| return false; |
| } |
| sample_out = stss_last_keyframe_; |
| } else { |
| // an absent stts means every frame is a key frame, we can provide sample |
| // directly. |
| sample_out = sample_number; |
| } |
| return true; |
| } |
| |
| // Set up map state and load first part of table, or entire table if it is small |
| // enough, for each of the supporated atoms. |
| bool ShellMP4Map::SetAtom(uint32 four_cc, |
| uint64 offset, |
| uint64 size, |
| uint32 cache_size_entries, |
| const uint8* atom) { |
| // All map atoms are variable-length tables starting with 4 bytes of |
| // version/flag info followed by a uint32 indicating the number of items in |
| // table. The stsz atom bucks tradition by putting an optional default value |
| // at index 4. |
| uint32 count = 0; |
| uint64 table_offset = offset + 8; |
| if (four_cc == kAtomType_stsz) { |
| if (size < 12) { |
| return false; |
| } |
| stsz_default_size_ = endian_util::load_uint32_big_endian(atom + 4); |
| count = endian_util::load_uint32_big_endian(atom + 8); |
| highest_valid_sample_number_ = |
| std::min(count - 1, highest_valid_sample_number_); |
| // if a non-zero default size is provided don't bother loading the table |
| if (stsz_default_size_) { |
| stsz_ = NULL; |
| return true; |
| } |
| |
| table_offset += 4; |
| } else { |
| if (size < 8) { |
| return false; |
| } |
| count = endian_util::load_uint32_big_endian(atom + 4); |
| } |
| |
| // if cache_size_entries is 0 we are to cache the entire table |
| if (cache_size_entries == 0) { |
| cache_size_entries = count; |
| } |
| |
| bool atom_init = false; |
| // initialize the appropriate table cache dependent on table type |
| switch (four_cc) { |
| case kAtomType_co64: |
| co64_ = new TableCache(table_offset, count, kEntrySize_co64, |
| cache_size_entries, reader_); |
| if (co64_) |
| atom_init = co64_Init(); |
| break; |
| |
| case kAtomType_ctts: |
| ctts_ = new TableCache(table_offset, count, kEntrySize_ctts, |
| cache_size_entries, reader_); |
| if (ctts_) |
| atom_init = ctts_Init(); |
| break; |
| |
| case kAtomType_stco: |
| stco_ = new TableCache(table_offset, count, kEntrySize_stco, |
| cache_size_entries, reader_); |
| if (stco_) |
| atom_init = stco_Init(); |
| break; |
| |
| case kAtomType_stsc: |
| stsc_ = new TableCache(table_offset, count, kEntrySize_stsc, |
| cache_size_entries, reader_); |
| if (stsc_) |
| atom_init = stsc_Init(); |
| break; |
| |
| case kAtomType_stss: |
| stss_ = new TableCache(table_offset, count, kEntrySize_stss, |
| cache_size_entries, reader_); |
| if (stss_) |
| atom_init = stss_Init(); |
| break; |
| |
| case kAtomType_stts: |
| stts_ = new TableCache(table_offset, count, kEntrySize_stts, |
| cache_size_entries, reader_); |
| if (stts_) |
| atom_init = stts_Init(); |
| break; |
| |
| case kAtomType_stsz: |
| stsz_ = new TableCache(table_offset, count, kEntrySize_stsz, |
| cache_size_entries, reader_); |
| if (stsz_) |
| atom_init = stsz_Init(); |
| break; |
| |
| default: |
| NOTREACHED() << "unknown atom type provided to mp4 map"; |
| break; |
| } |
| |
| return atom_init; |
| } |
| |
| bool ShellMP4Map::co64_Init() { |
| DCHECK(co64_); |
| // load offset of first chunk into current_chunk_offset_ |
| if (co64_->GetEntryCount() > 0) { |
| // can drop any stco table already allocated |
| stco_ = NULL; |
| // load initial value of current_chunk_offset_ for 0th chunk |
| return co64_->ReadU64Entry(0, ¤t_chunk_offset_); |
| } |
| |
| co64_ = NULL; |
| |
| return true; |
| } |
| |
| // The ctts table has the following per-entry layout: |
| // uint32 sample count |
| // uint32 composition offset in ticks |
| // |
| bool ShellMP4Map::ctts_Init() { |
| DCHECK(ctts_); |
| // get cache segment vector to reserve table entries in advance |
| int cache_segments = |
| (ctts_->GetEntryCount() / ctts_->GetCacheSizeEntries()) + 1; |
| ctts_samples_.reserve(cache_segments); |
| if (ctts_->GetEntryCount() > 0) { |
| // save the start of the first table integration at 0 |
| ctts_samples_.push_back(0); |
| ctts_table_index_ = 0; |
| ctts_first_sample_ = 0; |
| // load first entry in table, to start integration |
| return ctts_->ReadU32PairEntry(0, &ctts_next_first_sample_, |
| &ctts_sample_offset_); |
| } |
| // drop empty ctts_ table |
| ctts_ = NULL; |
| |
| return true; |
| } |
| |
| // To find the composition offset of a given sample number we must integrate |
| // through the ctts to find the range of samples containing sample_number. Note |
| // that the ctts is an optional table. |
| bool ShellMP4Map::ctts_AdvanceToSample(uint32 sample_number) { |
| // ctts table is optional, so treat not having one as non-fatal |
| if (!ctts_) { |
| return true; |
| } |
| // sample number could be before our saved first sample, meaning we've |
| // gone backward in sample numbers and will need to restart integration at |
| // the nearest saved sample count starting at a cache entry |
| if (sample_number < ctts_first_sample_) { |
| if (!ctts_SlipCacheToSample(sample_number, 0)) { |
| return false; |
| } |
| } |
| |
| // sample_number could also be ahead of our current range, for example when |
| // seeking forward. See if we've calculated these values ahead of us before, |
| // and if we can slip forward to them |
| int next_cache_index = (ctts_table_index_ / ctts_->GetCacheSizeEntries()) + 1; |
| if ((next_cache_index < ctts_samples_.size()) && |
| (sample_number >= ctts_samples_[next_cache_index])) { |
| if (!ctts_SlipCacheToSample(sample_number, next_cache_index)) { |
| return false; |
| } |
| } |
| |
| // perform integration until sample number is within correct ctts range |
| while (ctts_next_first_sample_ <= sample_number) { |
| // next first sample is now our the first sample |
| ctts_first_sample_ = ctts_next_first_sample_; |
| // advance to next entry in table |
| ctts_table_index_++; |
| // If this would be a new cache entry, keep a record of integration up |
| // to this point so we don't have to start from 0 on seeking back |
| if (!(ctts_table_index_ % ctts_->GetCacheSizeEntries())) { |
| int cache_index = ctts_table_index_ / ctts_->GetCacheSizeEntries(); |
| // check that this is our first time with these data |
| if (cache_index == ctts_samples_.size()) { |
| ctts_samples_.push_back(ctts_first_sample_); |
| } |
| // our integration at this point should always match any stored record |
| DCHECK_EQ(ctts_first_sample_, ctts_samples_[cache_index]); |
| } |
| |
| if (ctts_table_index_ < ctts_->GetEntryCount()) { |
| // load the sample count to determine next first sample |
| uint32 sample_count; |
| if (!ctts_->ReadU32PairEntry(ctts_table_index_, &sample_count, |
| &ctts_sample_offset_)) |
| return false; |
| ctts_next_first_sample_ = ctts_first_sample_ + sample_count; |
| } else { |
| // This means that the last entry in the table specified a sample range |
| // that this sample number has exceeded, and so the ctts of this sample |
| // number is undefined. While not a fatal error it's kind of a weird |
| // state, we set the offset back to zero and extend the next_first_sample |
| // to infinity |
| DLOG(WARNING) << base::StringPrintf( |
| "out of range sample number %d in ctts, last valid sample number: %d", |
| sample_number, ctts_next_first_sample_); |
| ctts_sample_offset_ = 0; |
| ctts_next_first_sample_ = UINT32_MAX; |
| break; |
| } |
| } |
| return true; |
| } |
| |
| bool ShellMP4Map::ctts_SlipCacheToSample(uint32 sample_number, |
| int starting_cache_index) { |
| DCHECK_LT(starting_cache_index, ctts_samples_.size()); |
| int cache_index = starting_cache_index; |
| for (; cache_index + 1 < ctts_samples_.size(); cache_index++) { |
| if (sample_number < ctts_samples_[cache_index + 1]) { |
| break; |
| } |
| } |
| ctts_first_sample_ = ctts_samples_[cache_index]; |
| ctts_table_index_ = cache_index * ctts_->GetCacheSizeEntries(); |
| // read sample count and duration to set next values |
| uint32 sample_count; |
| if (!ctts_->ReadU32PairEntry(ctts_table_index_, &sample_count, |
| &ctts_sample_offset_)) |
| return false; |
| ctts_next_first_sample_ = ctts_first_sample_ + sample_count; |
| return true; |
| } |
| |
| bool ShellMP4Map::stco_Init() { |
| DCHECK(stco_); |
| // load offset of first chunk into current_chunk_offset_ |
| if (stco_->GetEntryCount() > 0) { |
| co64_ = NULL; |
| return stco_->ReadU32EntryIntoU64(0, ¤t_chunk_offset_); |
| } |
| |
| stco_ = NULL; |
| |
| return true; |
| } |
| |
| // The stsc table has the following per-entry layout: |
| // uint32 first chunk number with this sample count |
| // uint32 samples-per-chunk |
| // uint32 sample description id (unused) |
| bool ShellMP4Map::stsc_Init() { |
| DCHECK(stsc_); |
| // set up vector to correct final size |
| int cache_segments = |
| (stsc_->GetEntryCount() / stsc_->GetCacheSizeEntries()) + 1; |
| stsc_sample_sums_.reserve(cache_segments); |
| // there must always be at least 1 entry in a valid stsc table |
| if (stsc_->GetEntryCount() > 0) { |
| stsc_first_chunk_ = 0; |
| stsc_first_chunk_sample_ = 0; |
| // first cached entry is always 0 |
| stsc_sample_sums_.push_back(0); |
| if (!stsc_->ReadU32PairEntry(0, NULL, &stsc_samples_per_chunk_)) { |
| stsc_ = NULL; |
| return false; |
| } |
| // look up next first chunk at next index in table |
| if (stsc_->GetEntryCount() > 1) { |
| if (!stsc_->ReadU32PairEntry(1, &stsc_next_first_chunk_, NULL)) { |
| stsc_ = NULL; |
| return false; |
| } |
| --stsc_next_first_chunk_; |
| stsc_next_first_chunk_sample_ = |
| stsc_next_first_chunk_ * stsc_samples_per_chunk_; |
| } else { |
| // every chunk in the file has the sample sample count, set next first |
| // chunk to highest valid chunk number. |
| stsc_next_first_chunk_ = UINT32_MAX; |
| stsc_next_first_chunk_sample_ = UINT32_MAX; |
| } |
| stsc_table_index_ = 0; |
| |
| // since we known the size of the first chunk we can set next_chunk_sample_ |
| next_chunk_sample_ = stsc_samples_per_chunk_; |
| |
| } else { |
| stsc_ = NULL; |
| } |
| |
| return true; |
| } |
| |
| // To find the chunk number of an abritrary sample we have to sum the |
| // samples-per-chunk value multiplied by the number of chunks with that sample |
| // count until the sum exceeds the sample number, then calculate the chunk |
| // number from that range of per-sample chunk sizes. Since this map is meant |
| // to be consumed incrementally and with minimal memory consumption we calculate |
| // this integration step only when needed, and save results for each cached |
| // piece of the table, to avoid having to recalculate needed data. |
| bool ShellMP4Map::stsc_AdvanceToSample(uint32 sample_number) { |
| DCHECK(stsc_); |
| // sample_number could be before first chunk, meaning that we are seeking |
| // backwards and have left the current chunk. Find the closest part of the |
| // cached table and integrate forward from there. |
| if (sample_number < stsc_first_chunk_sample_) { |
| if (!stsc_SlipCacheToSample(sample_number, 0)) { |
| return false; |
| } |
| } |
| |
| // sample_number could also be well head of our current piece of the |
| // cache, so see if we can re-use any previously calculated summations to |
| // skip to the nearest cache entry |
| int next_cache_index = (stsc_table_index_ / stsc_->GetCacheSizeEntries()) + 1; |
| if ((next_cache_index < stsc_sample_sums_.size()) && |
| (sample_number >= stsc_sample_sums_[next_cache_index])) { |
| if (!stsc_SlipCacheToSample(sample_number, next_cache_index)) { |
| return false; |
| } |
| } |
| |
| // Integrate through each table entry until we find sample_number in range |
| while (stsc_next_first_chunk_sample_ <= sample_number) { |
| // advance to next chunk sample range |
| stsc_first_chunk_sample_ = stsc_next_first_chunk_sample_; |
| // our next_first_chunk is now our first chunk |
| stsc_first_chunk_ = stsc_next_first_chunk_; |
| // advance to next entry in table |
| stsc_table_index_++; |
| // if we've advanced to a new segment of the cache, update the saved |
| // integration values |
| if (!(stsc_table_index_ % stsc_->GetCacheSizeEntries())) { |
| int cache_index = stsc_table_index_ / stsc_->GetCacheSizeEntries(); |
| // check that this is our first time with these data |
| if (cache_index == stsc_sample_sums_.size()) { |
| stsc_sample_sums_.push_back(stsc_first_chunk_sample_); |
| } |
| // our integration at this point should always match any stored record |
| DCHECK_EQ(stsc_first_chunk_sample_, stsc_sample_sums_[cache_index]); |
| } |
| if (stsc_table_index_ < stsc_->GetEntryCount()) { |
| // look up our new sample rate |
| if (!stsc_->ReadU32PairEntry(stsc_table_index_, NULL, |
| &stsc_samples_per_chunk_)) { |
| return false; |
| } |
| // we need to look up next table entry to determine next first chunk |
| if (stsc_table_index_ + 1 < stsc_->GetEntryCount()) { |
| // look up next first chunk |
| if (!stsc_->ReadU32PairEntry(stsc_table_index_ + 1, |
| &stsc_next_first_chunk_, NULL)) { |
| return false; |
| } |
| --stsc_next_first_chunk_; |
| // carry sum of first_samples forward to next chunk range |
| stsc_next_first_chunk_sample_ += |
| (stsc_next_first_chunk_ - stsc_first_chunk_) * |
| stsc_samples_per_chunk_; |
| } else { |
| // this is the normal place to encounter the end of the chunk table. |
| // set the next chunk to the highest valid chunk number |
| stsc_next_first_chunk_ = UINT32_MAX; |
| stsc_next_first_chunk_sample_ = UINT32_MAX; |
| } |
| } else { |
| // We should normally encounter the end of the chunk table on lookup |
| // of the next_first_chunk_ within the if clause associated with this |
| // else. Something has gone wrong. |
| NOTREACHED(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ShellMP4Map::stsc_SlipCacheToSample(uint32 sample_number, |
| int starting_cache_index) { |
| DCHECK_LT(starting_cache_index, stsc_sample_sums_.size()); |
| // look through old sample sums for the first entry that exceeds sample |
| // sample_number, we want the entry right before that |
| int cache_index = starting_cache_index; |
| for (; cache_index + 1 < stsc_sample_sums_.size(); cache_index++) { |
| if (sample_number < stsc_sample_sums_[cache_index + 1]) { |
| break; |
| } |
| } |
| // jump to new spot in table |
| stsc_first_chunk_sample_ = stsc_sample_sums_[cache_index]; |
| stsc_table_index_ = cache_index * stsc_->GetCacheSizeEntries(); |
| if (!stsc_->ReadU32PairEntry(stsc_table_index_, &stsc_first_chunk_, |
| &stsc_samples_per_chunk_)) { |
| return false; |
| } |
| // load current and next values |
| --stsc_first_chunk_; |
| if (stsc_table_index_ + 1 < stsc_->GetEntryCount()) { |
| if (!stsc_->ReadU32PairEntry(stsc_table_index_ + 1, &stsc_next_first_chunk_, |
| NULL)) { |
| return false; |
| } |
| --stsc_next_first_chunk_; |
| stsc_next_first_chunk_sample_ = |
| stsc_first_chunk_sample_ + |
| ((stsc_next_first_chunk_ - stsc_first_chunk_) * |
| stsc_samples_per_chunk_); |
| } else { |
| // We seem to have cached an entry 1 entry before end of the table, and |
| // are seeking to a region contained in that last entry in the table. |
| stsc_next_first_chunk_ = UINT32_MAX; |
| stsc_next_first_chunk_sample_ = UINT32_MAX; |
| } |
| return true; |
| } |
| |
| // stss is a list of sample numbers that are keyframes. |
| bool ShellMP4Map::stss_Init() { |
| int cache_segments = |
| (stss_->GetEntryCount() / stss_->GetCacheSizeEntries()) + 1; |
| stss_keyframes_.reserve(cache_segments); |
| // empty stss means every frame is a keyframe, same as not |
| // providing one |
| if (stss_->GetEntryCount() > 0) { |
| // identify first keyframe from first entry in stss |
| if (!stss_->ReadU32Entry(0, &stss_last_keyframe_)) { |
| stss_ = NULL; |
| return false; |
| } |
| --stss_last_keyframe_; |
| stss_keyframes_.push_back(stss_last_keyframe_); |
| stss_next_keyframe_ = stss_last_keyframe_; |
| stss_table_index_ = 0; |
| } else { |
| stss_ = NULL; |
| } |
| |
| return true; |
| } |
| |
| // advance by one table entry through stss, updating cache if necessary |
| bool ShellMP4Map::stss_AdvanceStep() { |
| DCHECK(stss_); |
| stss_last_keyframe_ = stss_next_keyframe_; |
| stss_table_index_++; |
| if (stss_table_index_ < stss_->GetEntryCount()) { |
| if (!stss_->ReadU32Entry(stss_table_index_, &stss_next_keyframe_)) { |
| return false; |
| } |
| --stss_next_keyframe_; |
| if (!(stss_table_index_ % stss_->GetCacheSizeEntries())) { |
| int cache_index = stss_table_index_ / stss_->GetCacheSizeEntries(); |
| // only add if this is the first time we've encountered this number |
| if (cache_index == stss_keyframes_.size()) { |
| stss_keyframes_.push_back(stss_next_keyframe_); |
| } |
| DCHECK_EQ(stss_next_keyframe_, stss_keyframes_[cache_index]); |
| } |
| } else { |
| stss_next_keyframe_ = UINT32_MAX; |
| } |
| return true; |
| } |
| |
| bool ShellMP4Map::stss_FindNearestKeyframe(uint32 sample_number) { |
| DCHECK(stss_); |
| // it is assumed that there's at least one cache entry created by |
| // stss_Init(); |
| DCHECK_GT(stss_keyframes_.size(), 0); |
| int cache_entry_number = stss_keyframes_.size() - 1; |
| int total_cache_entries = |
| (stss_->GetEntryCount() + stss_->GetCacheSizeEntries() - 1) / |
| stss_->GetCacheSizeEntries(); |
| // if there's more than one cache entry we can search the cached |
| // entries for the entry containing our keyframe, otherwise we skip |
| // directly to the binary search of the single cached entry |
| if (total_cache_entries > 1) { |
| // if the sample number resides within the range of cached entries |
| // we search those to find right table cache entry to load |
| if (sample_number < stss_keyframes_[cache_entry_number]) { |
| int lower_bound = 0; |
| int upper_bound = stss_keyframes_.size(); |
| // binary search to find range |
| while (lower_bound <= upper_bound) { |
| cache_entry_number = lower_bound + ((upper_bound - lower_bound) / 2); |
| if (sample_number < stss_keyframes_[cache_entry_number]) { |
| upper_bound = cache_entry_number - 1; |
| } else { // sample_number >= stss_keyframes_[cache_entry_number] |
| // if we are at end of list or next cache entry is higher than sample |
| // number we consider it a match |
| if (cache_entry_number == stss_keyframes_.size() - 1 || |
| sample_number < stss_keyframes_[cache_entry_number + 1]) { |
| break; |
| } |
| lower_bound = cache_entry_number + 1; |
| } |
| } |
| } |
| // We've gotten as close as we can using the cached values and must handle |
| // two cases. (a) is that we know that sample_number is contained in the |
| // cache_entry_number, because we know that: |
| // stts_keyframes_[cache_entry_number] <= sample_number < |
| // stts_keyframes_[cache_entry_number + 1] |
| // (b) is that we only know: |
| // stts_keyframes_[stts_keyframes_.size() - 1] <= sample_number |
| // because we have not cached an upper bound to sample_number. |
| // First step is to make (b) in to (a) by advancing through cache entries |
| // until last table entry in cache > sample_number or until we arrive |
| // at the cache entry in the table. |
| while ((cache_entry_number == stss_keyframes_.size() - 1) && |
| cache_entry_number < total_cache_entries - 1) { |
| // Use the first key frame in next cache as upper bound. |
| int next_cached_entry_number = |
| (cache_entry_number + 1) * stss_->GetCacheSizeEntries(); |
| uint32 next_cached_keyframe; |
| if (!stss_->ReadU32Entry(next_cached_entry_number, |
| &next_cached_keyframe)) { |
| return false; |
| } |
| --next_cached_keyframe; |
| // if this keyframe is higher than our sample number we're in the right |
| // table, stop |
| if (sample_number < next_cached_keyframe) { |
| break; |
| } |
| // ok, we need to look in to the next cache entry, advance |
| cache_entry_number++; |
| int first_table_entry_number = |
| cache_entry_number * stss_->GetCacheSizeEntries(); |
| uint32 first_keyframe_in_cache_entry; |
| if (!stss_->ReadU32Entry(first_table_entry_number, |
| &first_keyframe_in_cache_entry)) { |
| return false; |
| } |
| --first_keyframe_in_cache_entry; |
| // save first entry in keyframe cache |
| stss_keyframes_.push_back(first_keyframe_in_cache_entry); |
| } |
| // make sure we have an upper bound |
| if (cache_entry_number != total_cache_entries - 1 && |
| cache_entry_number == stss_keyframes_.size() - 1) { |
| int next_cached_entry_number = |
| ((cache_entry_number + 1) * stss_->GetCacheSizeEntries()); |
| uint32 next_cached_keyframe; |
| if (!stss_->ReadU32Entry(next_cached_entry_number, |
| &next_cached_keyframe)) { |
| return false; |
| } |
| --next_cached_keyframe; |
| stss_keyframes_.push_back(next_cached_keyframe); |
| } |
| // ok, now we assume we are in state (a), and that we're either |
| // at the end of the table or within the cache entry bounds for our |
| // sample number |
| DCHECK(stss_keyframes_[cache_entry_number] <= sample_number && |
| (cache_entry_number == total_cache_entries - 1 || |
| sample_number < stss_keyframes_[cache_entry_number + 1])); |
| } |
| // binary search within stss cache entry for keyframes bounding sample_number |
| int lower_bound = cache_entry_number * stss_->GetCacheSizeEntries(); |
| int upper_bound = std::min(lower_bound + stss_->GetCacheSizeEntries(), |
| stss_->GetEntryCount()); |
| |
| while (lower_bound <= upper_bound) { |
| stss_table_index_ = lower_bound + ((upper_bound - lower_bound) / 2); |
| if (!stss_->ReadU32Entry(stss_table_index_, &stss_last_keyframe_)) { |
| return false; |
| } |
| --stss_last_keyframe_; |
| if (sample_number < stss_last_keyframe_) { |
| upper_bound = stss_table_index_ - 1; |
| } else { // sample_number >= last_keyframe |
| lower_bound = stss_table_index_ + 1; |
| // if this is the last entry in the table, we can stop here. |
| if (lower_bound == stss_->GetEntryCount()) { |
| stss_next_keyframe_ = UINT32_MAX; |
| break; |
| } |
| // load next entry in table, see if we actually found the upper bound |
| if (!stss_->ReadU32Entry(lower_bound, &stss_next_keyframe_)) { |
| return false; |
| } |
| --stss_next_keyframe_; |
| if (sample_number < stss_next_keyframe_) { |
| stss_table_index_ = lower_bound; |
| break; |
| } |
| } |
| } |
| return sample_number >= stss_last_keyframe_ && |
| sample_number < stss_next_keyframe_; |
| } |
| |
| // The stts table has the following per-entry layout: |
| // uint32 sample count - number of sequential samples with this duration |
| // uint32 sample duration - duration in ticks of this sample range |
| bool ShellMP4Map::stts_Init() { |
| int cache_segments = |
| (stts_->GetEntryCount() / stts_->GetCacheSizeEntries()) + 1; |
| stts_samples_.reserve(cache_segments); |
| stts_timestamps_.reserve(cache_segments); |
| // need at least one entry in valid stts |
| if (stts_->GetEntryCount() > 0) { |
| // integration starts at 0 for both cache entries |
| stts_samples_.push_back(0); |
| stts_timestamps_.push_back(0); |
| if (!stts_->ReadU32PairEntry(0, &stts_next_first_sample_, |
| &stts_sample_duration_)) { |
| stts_ = NULL; |
| return false; |
| } |
| stts_first_sample_ = 0; |
| stts_first_sample_time_ = 0; |
| stts_next_first_sample_time_ = |
| stts_next_first_sample_ * stts_sample_duration_; |
| stts_table_index_ = 0; |
| } else { |
| stts_ = NULL; |
| } |
| |
| return true; |
| } |
| |
| bool ShellMP4Map::stts_AdvanceToSample(uint32 sample_number) { |
| DCHECK(stts_); |
| // sample_number could be before our current sample range, in which case |
| // we skip to the nearest table entry before sample_number and integrate |
| // forward to the sample_number again. |
| if (sample_number < stts_first_sample_) { |
| if (!stts_SlipCacheToSample(sample_number, 0)) { |
| return false; |
| } |
| } |
| |
| // sample number could also be well ahead of this cache segment, if we've |
| // previously calculated summations ahead let's skip to the correct one |
| int next_cache_index = (stts_table_index_ / stts_->GetCacheSizeEntries()) + 1; |
| if ((next_cache_index < stts_samples_.size()) && |
| (sample_number >= stts_samples_[next_cache_index])) { |
| if (!stts_SlipCacheToSample(sample_number, next_cache_index)) { |
| return false; |
| } |
| } |
| |
| // integrate through the stts until sample_number is within current range |
| while (stts_next_first_sample_ <= sample_number) { |
| if (!stts_IntegrateStep()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Move our integration steps to a previously saved entry in the cache tables. |
| // Searches linearly through the vector of old cached values, so can accept a |
| // starting index to do the search from. |
| bool ShellMP4Map::stts_SlipCacheToSample(uint32 sample_number, |
| int starting_cache_index) { |
| DCHECK_LT(starting_cache_index, stts_samples_.size()); |
| int cache_index = starting_cache_index; |
| for (; cache_index + 1 < stts_samples_.size(); cache_index++) { |
| if (sample_number < stts_samples_[cache_index + 1]) { |
| break; |
| } |
| } |
| stts_first_sample_ = stts_samples_[cache_index]; |
| stts_first_sample_time_ = stts_timestamps_[cache_index]; |
| stts_table_index_ = cache_index * stts_->GetCacheSizeEntries(); |
| uint32 sample_count; |
| // read sample count and duration to set next values |
| if (!stts_->ReadU32PairEntry(stts_table_index_, &sample_count, |
| &stts_sample_duration_)) { |
| return false; |
| } |
| stts_next_first_sample_ = stts_first_sample_ + sample_count; |
| stts_next_first_sample_time_ = |
| stts_first_sample_time_ + (sample_count * stts_sample_duration_); |
| return true; |
| } |
| |
| bool ShellMP4Map::stts_AdvanceToTime(uint64 timestamp) { |
| DCHECK(stts_); |
| |
| if (timestamp < stts_first_sample_time_) { |
| if (!stts_SlipCacheToTime(timestamp, 0)) { |
| return false; |
| } |
| } |
| |
| // sample number could also be well ahead of this cache segment, if we've |
| // previously calculated summations ahead let's skip to the correct one |
| int next_cache_index = (stts_table_index_ / stts_->GetCacheSizeEntries()) + 1; |
| if ((next_cache_index < stts_timestamps_.size()) && |
| (timestamp >= stts_timestamps_[next_cache_index])) { |
| if (!stts_SlipCacheToTime(timestamp, next_cache_index)) { |
| return false; |
| } |
| } |
| |
| // integrate through the stts until sample_number is within current range |
| while (stts_next_first_sample_time_ <= timestamp) { |
| if (!stts_IntegrateStep()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ShellMP4Map::stts_IntegrateStep() { |
| // advance time to next sample range |
| uint32 range_size = stts_next_first_sample_ - stts_first_sample_; |
| stts_first_sample_time_ += (range_size * stts_sample_duration_); |
| // advance sample counter to next range |
| stts_first_sample_ = stts_next_first_sample_; |
| // bump table counter to next entry |
| stts_table_index_++; |
| // see if we just crossed a cache boundary and should cache results |
| if (!(stts_table_index_ % stts_->GetCacheSizeEntries())) { |
| int cache_index = stts_table_index_ / stts_->GetCacheSizeEntries(); |
| // check that this is our first time with these data |
| if (cache_index == stts_samples_.size()) { |
| // both tables should always grow together |
| DCHECK_EQ(stts_samples_.size(), stts_timestamps_.size()); |
| stts_samples_.push_back(stts_first_sample_); |
| stts_timestamps_.push_back(stts_first_sample_time_); |
| } |
| // our integration at this point should always match any stored record |
| DCHECK_EQ(stts_first_sample_, stts_samples_[cache_index]); |
| DCHECK_EQ(stts_first_sample_time_, stts_timestamps_[cache_index]); |
| } |
| if (stts_table_index_ < stts_->GetEntryCount()) { |
| // load next entry data |
| uint32 sample_count; |
| if (!stts_->ReadU32PairEntry(stts_table_index_, &sample_count, |
| &stts_sample_duration_)) { |
| return false; |
| } |
| // calculate next sample number from range size |
| stts_next_first_sample_ = stts_first_sample_ + sample_count; |
| // and load duration of this sample range |
| stts_next_first_sample_time_ = |
| stts_first_sample_time_ + (sample_count * stts_sample_duration_); |
| } else { |
| // We've gone beyond the range defined by the last entry in the stts. |
| // this is an error. |
| highest_valid_sample_number_ = |
| std::min(highest_valid_sample_number_, stts_first_sample_ - 1); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ShellMP4Map::stts_SlipCacheToTime(uint64 timestamp, |
| int starting_cache_index) { |
| DCHECK_LT(starting_cache_index, stts_timestamps_.size()); |
| int cache_index = starting_cache_index; |
| for (; cache_index + 1 < stts_timestamps_.size(); cache_index++) { |
| if (timestamp < stts_timestamps_[cache_index + 1]) { |
| break; |
| } |
| } |
| stts_first_sample_ = stts_samples_[cache_index]; |
| stts_first_sample_time_ = stts_timestamps_[cache_index]; |
| stts_table_index_ = cache_index * stts_->GetCacheSizeEntries(); |
| // read sample count and duration to set next values |
| uint32 sample_count; |
| if (!stts_->ReadU32PairEntry(stts_table_index_, &sample_count, |
| &stts_sample_duration_)) { |
| return false; |
| } |
| stts_next_first_sample_ = stts_first_sample_ + sample_count; |
| stts_next_first_sample_time_ = |
| stts_first_sample_time_ + (sample_count * stts_sample_duration_); |
| return true; |
| } |
| |
| bool ShellMP4Map::stsz_Init() { |
| return stsz_->GetBytesAtEntry(0) != NULL; |
| } |
| |
| } // namespace media |