blob: 1d8b8d6039e39034b73f8ad9939cb2930e58a729 [file] [log] [blame]
/*
* 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, &current_chunk_offset_))
return false;
} else if (!stco_->ReadU32EntryIntoU64(chunk_number,
&current_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, &current_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, &current_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