diff --git a/media/mp4/box_definitions.cc b/media/mp4/box_definitions.cc index e7f169323b..370de7cfa5 100644 --- a/media/mp4/box_definitions.cc +++ b/media/mp4/box_definitions.cc @@ -245,13 +245,198 @@ bool SampleDescription::Parse(BoxReader* reader) { return true; } +DecodingTimeToSample::DecodingTimeToSample() {} +DecodingTimeToSample::~DecodingTimeToSample() {} +FourCC DecodingTimeToSample::BoxType() const { return FOURCC_STTS; } + +bool DecodingTimeToSample::Parse(BoxReader* reader) { + uint32 count; + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&count)); + + decoding_time.resize(count); + for (int i = 0; i < count; ++i) { + RCHECK(reader->Read4(&decoding_time[i].sample_count) && + reader->Read4(&decoding_time[i].sample_delta)); + } + return true; +} + +CompositionTimeToSample::CompositionTimeToSample() {} +CompositionTimeToSample::~CompositionTimeToSample() {} +FourCC CompositionTimeToSample::BoxType() const { return FOURCC_CTTS; } + +bool CompositionTimeToSample::Parse(BoxReader* reader) { + uint32 count; + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&count)); + + composition_offset.resize(count); + for (int i = 0; i < count; ++i) { + RCHECK(reader->Read4(&composition_offset[i].sample_count) && + reader->Read4s(&composition_offset[i].sample_offset)); + } + return true; +} + +SampleToChunk::SampleToChunk() {} +SampleToChunk::~SampleToChunk() {} +FourCC SampleToChunk::BoxType() const { return FOURCC_STSC; } + +bool SampleToChunk::Parse(BoxReader* reader) { + uint32 count; + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&count)); + + chunk_info.resize(count); + for (int i = 0; i < count; ++i) { + RCHECK(reader->Read4(&chunk_info[i].first_chunk) && + reader->Read4(&chunk_info[i].samples_per_chunk) && + reader->Read4(&chunk_info[i].sample_description_index)); + // first_chunk values are always increasing. + DCHECK(i == 0 ? chunk_info[i].first_chunk == 1 + : chunk_info[i].first_chunk > chunk_info[i - 1].first_chunk); + } + return true; +} + +SampleSize::SampleSize() : sample_size(0), sample_count(0) {} +SampleSize::~SampleSize() {} +FourCC SampleSize::BoxType() const { return FOURCC_STSZ; } + +bool SampleSize::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&sample_size) && + reader->Read4(&sample_count)); + + if (sample_size == 0) { + sizes.resize(sample_count); + for (int i = 0; i < sample_count; ++i) + RCHECK(reader->Read4(&sizes[i])); + } + return true; +} + +CompactSampleSize::CompactSampleSize() : SampleSize() {} +CompactSampleSize::~CompactSampleSize() {} +FourCC CompactSampleSize::BoxType() const { return FOURCC_STZ2; } + +bool CompactSampleSize::Parse(BoxReader* reader) { + uint8 field_size; + RCHECK(reader->ReadFullBoxHeader() && + reader->SkipBytes(3) && + reader->Read1(&field_size) && + reader->Read4(&sample_count)); + + // Reserve one more entry if field size is 4 bits. + sizes.resize(sample_count + (field_size == 4 ? 1 : 0)); + switch (field_size) { + case 4: + for (int i = 0; i < sample_count; ++i) { + uint8 size; + RCHECK(reader->Read1(&size)); + sizes[i] = size >> 4; + sizes[++i] = size & 0x0F; + } + break; + case 8: + for (int i = 0; i < sample_count; ++i) { + uint8 size; + RCHECK(reader->Read1(&size)); + sizes[i] = size; + } + break; + case 16: + for (int i = 0; i < sample_count; ++i) { + uint16 size; + RCHECK(reader->Read2(&size)); + sizes[i] = size; + } + break; + default: + RCHECK(false); + } + sizes.resize(sample_count); + return true; +} + +ChunkOffset::ChunkOffset() {} +ChunkOffset::~ChunkOffset() {} +FourCC ChunkOffset::BoxType() const { return FOURCC_STCO; } + +bool ChunkOffset::Parse(BoxReader* reader) { + uint32 count; + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&count)); + + offsets.resize(count); + for (int i = 0; i < count; ++i) + RCHECK(reader->Read4Into8(&offsets[i])); + return true; +} + +ChunkLargeOffset::ChunkLargeOffset() {} +ChunkLargeOffset::~ChunkLargeOffset() {} +FourCC ChunkLargeOffset::BoxType() const { return FOURCC_CO64; } + +bool ChunkLargeOffset::Parse(BoxReader* reader) { + uint32 count; + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&count)); + + offsets.resize(count); + for (int i = 0; i < count; ++i) + RCHECK(reader->Read8(&offsets[i])); + return true; +} + +SyncSample::SyncSample() {} +SyncSample::~SyncSample() {} +FourCC SyncSample::BoxType() const { return FOURCC_STSS; } + +bool SyncSample::Parse(BoxReader* reader) { + uint32 count; + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&count)); + + sample_number.resize(count); + for (int i = 0; i < count; ++i) + RCHECK(reader->Read4(&sample_number[i])); + return true; +} + SampleTable::SampleTable() {} SampleTable::~SampleTable() {} FourCC SampleTable::BoxType() const { return FOURCC_STBL; } bool SampleTable::Parse(BoxReader* reader) { - return reader->ScanChildren() && - reader->ReadChild(&description); + RCHECK(reader->ScanChildren() && + reader->ReadChild(&description) && + reader->ReadChild(&decoding_time_to_sample) && + reader->MaybeReadChild(&composition_time_to_sample) && + reader->MaybeReadChild(&sync_sample) && + reader->ReadChild(&sample_to_chunk)); + + // Either SampleSize or CompactSampleSize must present. + if (reader->ChildExist(&sample_size)) { + RCHECK(reader->ReadChild(&sample_size)); + } else { + CompactSampleSize compact_sample_size; + RCHECK(reader->ReadChild(&compact_sample_size)); + sample_size.sample_size = compact_sample_size.sample_size; + sample_size.sample_count = compact_sample_size.sample_count; + sample_size.sizes.swap(compact_sample_size.sizes); + } + + // Either ChunkOffset or ChunkLargeOffset must present. + if (reader->ChildExist(&chunk_offset)) { + RCHECK(reader->ReadChild(&chunk_offset)); + } else { + ChunkLargeOffset chunk_large_offset; + RCHECK(reader->ReadChild(&chunk_large_offset)); + chunk_offset.offsets.swap(chunk_large_offset.offsets); + } + return true; } EditList::EditList() {} @@ -592,7 +777,7 @@ bool Movie::Parse(BoxReader* reader) { reader->ReadChild(&header) && reader->ReadChildren(&tracks) && // Media Source specific: 'mvex' required - reader->ReadChild(&extends) && + reader->MaybeReadChild(&extends) && reader->MaybeReadChildren(&pssh); } diff --git a/media/mp4/box_definitions.h b/media/mp4/box_definitions.h index 6d03c3e635..9d1f98b78a 100644 --- a/media/mp4/box_definitions.h +++ b/media/mp4/box_definitions.h @@ -209,14 +209,95 @@ struct SampleDescription : Box { std::vector audio_entries; }; +struct DecodingTime { + uint32 sample_count; + uint32 sample_delta; +}; + +// stts. +struct DecodingTimeToSample : Box { + DECLARE_BOX_METHODS(DecodingTimeToSample); + + std::vector decoding_time; +}; + +struct CompositionOffset { + uint32 sample_count; + // If version == 0, sample_offset is uint32; + // If version == 1, sample_offset is int32. + // Let us always use signed version, which should work unless the offset + // exceeds 31 bits, which shouldn't happen. + int32 sample_offset; +}; + +// ctts. Optional. +struct CompositionTimeToSample : Box { + DECLARE_BOX_METHODS(CompositionTimeToSample); + + std::vector composition_offset; +}; + +struct ChunkInfo { + uint32 first_chunk; + uint32 samples_per_chunk; + uint32 sample_description_index; +}; + +// stsc. +struct SampleToChunk : Box { + DECLARE_BOX_METHODS(SampleToChunk); + + std::vector chunk_info; +}; + +// stsz. +struct SampleSize : Box { + DECLARE_BOX_METHODS(SampleSize); + + uint32 sample_size; + uint32 sample_count; + std::vector sizes; +}; + +// stz2. +struct CompactSampleSize : SampleSize { + DECLARE_BOX_METHODS(CompactSampleSize); +}; + +// stco. +struct ChunkOffset : Box { + DECLARE_BOX_METHODS(ChunkOffset); + + // Chunk byte offsets into mdat relative to the beginning of the file. + // Use 64 bits instead of 32 bits so it is large enough to hold + // ChunkLargeOffset data. + std::vector offsets; +}; + +// co64. +struct ChunkLargeOffset : ChunkOffset { + DECLARE_BOX_METHODS(ChunkLargeOffset); +}; + +// stss. Optional. +struct SyncSample : Box { + DECLARE_BOX_METHODS(SyncSample); + + std::vector sample_number; +}; + struct SampleTable : Box { DECLARE_BOX_METHODS(SampleTable); - // Media Source specific: we ignore many of the sub-boxes in this box, - // including some that are required to be present in the BMFF spec. This - // includes the 'stts', 'stsc', and 'stco' boxes, which must contain no - // samples in order to be compliant files. SampleDescription description; + DecodingTimeToSample decoding_time_to_sample; + CompositionTimeToSample composition_time_to_sample; + SampleToChunk sample_to_chunk; + // Either SampleSize or CompactSampleSize must present. Store in SampleSize. + SampleSize sample_size; + // Either ChunkOffset or ChunkLargeOffset must present. Store in ChunkOffset. + ChunkOffset chunk_offset; + SyncSample sync_sample; }; struct MediaHeader : Box { diff --git a/media/mp4/box_reader.cc b/media/mp4/box_reader.cc index 6bf438bc0d..e506259241 100644 --- a/media/mp4/box_reader.cc +++ b/media/mp4/box_reader.cc @@ -187,6 +187,10 @@ bool BoxReader::ReadChild(Box* child) { return true; } +bool BoxReader::ChildExist(Box* child) { + return children_.count(child->BoxType()) > 0; +} + bool BoxReader::MaybeReadChild(Box* child) { if (!children_.count(child->BoxType())) return true; return ReadChild(child); diff --git a/media/mp4/box_reader.h b/media/mp4/box_reader.h index 759d89a75c..ec723e1530 100644 --- a/media/mp4/box_reader.h +++ b/media/mp4/box_reader.h @@ -100,6 +100,9 @@ class BoxReader : public BufferReader { // buffer position. Must be called before any of the *Child functions work. bool ScanChildren() WARN_UNUSED_RESULT; + // Return true if child with type |child.BoxType()| exists. + bool ChildExist(Box* child) WARN_UNUSED_RESULT; + // Read exactly one child box from the set of children. The type of the child // will be determined by the BoxType() method of |child|. bool ReadChild(Box* child) WARN_UNUSED_RESULT; diff --git a/media/mp4/chunk_info_iterator.cc b/media/mp4/chunk_info_iterator.cc new file mode 100644 index 0000000000..50f3a8b91a --- /dev/null +++ b/media/mp4/chunk_info_iterator.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/chunk_info_iterator.h" + +#include +#include + +#include "base/logging.h" + +namespace media { +namespace mp4 { + +ChunkInfoIterator::ChunkInfoIterator(const SampleToChunk& sample_to_chunk) + : chunk_sample_index_(0), + current_chunk_(0), + chunk_info_table_(sample_to_chunk.chunk_info), + iterator_(chunk_info_table_.begin()) { + if (iterator_ != chunk_info_table_.end()) + current_chunk_ = iterator_->first_chunk; +} + +bool ChunkInfoIterator::AdvanceChunk() { + ++current_chunk_; + if (iterator_ + 1 != chunk_info_table_.end()) { + if (current_chunk_ >= (iterator_ + 1)->first_chunk) + ++iterator_; + } + chunk_sample_index_ = 0; + return true; +} + +bool ChunkInfoIterator::AdvanceSample() { + ++chunk_sample_index_; + if (chunk_sample_index_ >= iterator_->samples_per_chunk) + AdvanceChunk(); + return true; +} + +bool ChunkInfoIterator::IsValid() { + return iterator_ != chunk_info_table_.end() + && chunk_sample_index_ < iterator_->samples_per_chunk; +} + +uint32 ChunkInfoIterator::NumSamples(uint32 start_chunk, uint32 end_chunk) { + DCHECK(start_chunk <= end_chunk); + uint32 last_chunk = 0; + uint32 num_samples = 0; + for (std::vector::const_iterator it = chunk_info_table_.begin(); + it != chunk_info_table_.end(); ++it) { + last_chunk = ( + (it + 1 == chunk_info_table_.end()) ? + std::numeric_limits::max() : (it + 1)->first_chunk) - 1; + if (last_chunk >= start_chunk) { + num_samples += (std::min(end_chunk, last_chunk) + - std::max(start_chunk, it->first_chunk) + 1) + * it->samples_per_chunk; + if (last_chunk >= end_chunk) + break; + } + } + return num_samples; +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/chunk_info_iterator.h b/media/mp4/chunk_info_iterator.h new file mode 100644 index 0000000000..02c0a6f960 --- /dev/null +++ b/media/mp4/chunk_info_iterator.h @@ -0,0 +1,72 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Implements a wrapper around Sample to Chunk Box (STSC) to iterate through +// the compressed table by sample/chunk. This class also provides a convenient +// function to query total number of samples from start_chunk to end_chunk. + +#ifndef MEDIA_MP4_CHUNK_INFO_ITERATOR_H_ +#define MEDIA_MP4_CHUNK_INFO_ITERATOR_H_ + +#include + +#include "media/mp4/box_definitions.h" + +namespace media { +namespace mp4 { + +class ChunkInfoIterator { + public: + explicit ChunkInfoIterator(const SampleToChunk& sample_to_chunk); + + // Advance the properties to refer to the next chunk. Return status + // indicating whether the chunk is still valid. + bool AdvanceChunk(); + + // Advance the properties to refer to the next sample. Return status + // indicating whether the sample is still valid. + bool AdvanceSample(); + + // Return whether the current chunk is valid. + bool IsValid(); + + // Return current chunk. + uint32 current_chunk() { + return current_chunk_; + } + + // Return samples per chunk for current chunk. + uint32 samples_per_chunk() { + return iterator_->samples_per_chunk; + } + + // Return sample description index for current chunk. + uint32 sample_description_index() { + return iterator_->sample_description_index; + } + + // Return number of samples from start_chunk to end_chunk, both 1-based, + // inclusive. + uint32 NumSamples(uint32 start_chunk, uint32 end_chunk); + + // Return the last first_chunk in chunk_info_table. + uint32 LastFirstChunk() { + if (chunk_info_table_.size() == 0) + return 0; + return (chunk_info_table_.end() - 1)->first_chunk; + } + + private: + uint32 chunk_sample_index_; + uint32 current_chunk_; + const std::vector& chunk_info_table_; + std::vector::const_iterator iterator_; + + DISALLOW_COPY_AND_ASSIGN(ChunkInfoIterator); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_CHUNK_INFO_ITERATOR_H_ diff --git a/media/mp4/chunk_info_iterator_unittest.cc b/media/mp4/chunk_info_iterator_unittest.cc new file mode 100644 index 0000000000..d9432e46fc --- /dev/null +++ b/media/mp4/chunk_info_iterator_unittest.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/chunk_info_iterator.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +struct ChunkProperty { + uint32 samples_per_chunk; + uint32 sample_description_index; +}; +} // namespace + +namespace media { +namespace mp4 { + +const uint32 kNumChunks = 100; +const ChunkInfo kChunkInfos[] = + {{1, 8, 1}, {9, 5, 1}, {25, 7, 2}, {48, 63, 2}, {80, 2, 1}}; + +class ChunkInfoIteratorTest : public testing::Test { + public: + ChunkInfoIteratorTest() { + // Build chunk info table from kChunkInfos. + uint32 length = sizeof(kChunkInfos) / sizeof(ChunkInfo); + CHECK(kChunkInfos[0].first_chunk == 1); + CHECK(kChunkInfos[length - 1].first_chunk <= kNumChunks); + uint32 chunk_index = kChunkInfos[0].first_chunk; + for (uint32 i = 0; i < length; ++i) { + uint32 next_first_chunk = + (i == length - 1) ? kNumChunks + 1 : kChunkInfos[i + 1].first_chunk; + for (; chunk_index < next_first_chunk; ++chunk_index) { + ChunkProperty chunk = {kChunkInfos[i].samples_per_chunk, kChunkInfos[i].sample_description_index}; + //chunk.samples_per_chunk = kChunkInfos[i].samples_per_chunk; + //chunk.sample_description_index = kChunkInfos[i].sample_description_index; + chunk_info_table_.push_back(chunk); + } + } + + sample_to_chunk_.chunk_info.assign(kChunkInfos, kChunkInfos + length); + chunk_info_iterator_.reset(new ChunkInfoIterator(sample_to_chunk_)); + } + + protected: + std::vector chunk_info_table_; + SampleToChunk sample_to_chunk_; + scoped_ptr chunk_info_iterator_; + + private: + DISALLOW_COPY_AND_ASSIGN(ChunkInfoIteratorTest); +}; + +TEST_F(ChunkInfoIteratorTest, EmptyChunkInfo) { + SampleToChunk sample_to_chunk; + ChunkInfoIterator iterator(sample_to_chunk); + EXPECT_FALSE(iterator.IsValid()); + EXPECT_EQ(0, iterator.LastFirstChunk()); +} + +TEST_F(ChunkInfoIteratorTest, LastFirstChunk) { + ASSERT_EQ( + kChunkInfos[sizeof(kChunkInfos) / sizeof(ChunkInfo) - 1].first_chunk, + chunk_info_iterator_->LastFirstChunk()); +} + +TEST_F(ChunkInfoIteratorTest, NumSamples) { + for (uint32 i = 0; i < kNumChunks; ++i) { + for (uint32 num_samples = 0, j = i; j < kNumChunks; ++j) { + num_samples += chunk_info_table_[j].samples_per_chunk; + ASSERT_EQ(num_samples, chunk_info_iterator_->NumSamples(i + 1, j + 1)); + } + } +} + +TEST_F(ChunkInfoIteratorTest, AdvanceChunk) { + for (uint32 chunk = 0; chunk < kNumChunks; ++chunk) { + ASSERT_TRUE(chunk_info_iterator_->IsValid()); + EXPECT_EQ(chunk + 1, chunk_info_iterator_->current_chunk()); + EXPECT_EQ(chunk_info_table_[chunk].samples_per_chunk, + chunk_info_iterator_->samples_per_chunk()); + EXPECT_EQ(chunk_info_table_[chunk].sample_description_index, + chunk_info_iterator_->sample_description_index()); + // Will always succeed as Sample to Chunk Box does not define the last + // chunk. + ASSERT_TRUE(chunk_info_iterator_->AdvanceChunk()); + } +} + +TEST_F(ChunkInfoIteratorTest, AdvanceSample) { + for (uint32 chunk = 0; chunk < kNumChunks; ++chunk) { + uint32 samples_per_chunk = chunk_info_table_[chunk].samples_per_chunk; + for (uint32 sample = 0; sample < samples_per_chunk; ++sample) { + ASSERT_TRUE(chunk_info_iterator_->IsValid()); + EXPECT_EQ(chunk + 1, chunk_info_iterator_->current_chunk()); + EXPECT_EQ(chunk_info_table_[chunk].samples_per_chunk, + chunk_info_iterator_->samples_per_chunk()); + EXPECT_EQ(chunk_info_table_[chunk].sample_description_index, + chunk_info_iterator_->sample_description_index()); + // Will always succeed as Sample to Chunk Box does not define the last + // chunk. + ASSERT_TRUE(chunk_info_iterator_->AdvanceSample()); + } + } +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/composition_offset_iterator.cc b/media/mp4/composition_offset_iterator.cc new file mode 100644 index 0000000000..84a396dd18 --- /dev/null +++ b/media/mp4/composition_offset_iterator.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/composition_offset_iterator.h" + +#include "base/logging.h" + +namespace media { +namespace mp4 { + +CompositionOffsetIterator::CompositionOffsetIterator( + const CompositionTimeToSample& composition_time_to_sample) + : sample_index_(0), + composition_offset_table_(composition_time_to_sample.composition_offset), + iterator_(composition_offset_table_.begin()) { +} + +bool CompositionOffsetIterator::AdvanceSample() { + ++sample_index_; + if (sample_index_ >= iterator_->sample_count) { + ++iterator_; + if (iterator_ == composition_offset_table_.end()) + return false; + sample_index_ = 0; + } + return true; +} + +bool CompositionOffsetIterator::IsValid() { + return iterator_ != composition_offset_table_.end() + && sample_index_ < iterator_->sample_count; +} + +uint32 CompositionOffsetIterator::SampleOffset(uint32 sample) { + uint32 current_sample = 0; + std::vector::const_iterator it = + composition_offset_table_.begin(); + for (; it != composition_offset_table_.end(); ++it) { + current_sample += it->sample_count; + if (current_sample >= sample) + return it->sample_offset; + } + DCHECK_LE(sample, current_sample) << " Sample is invalid"; + return 0; +} + +uint32 CompositionOffsetIterator::NumSamples() { + uint32 num_samples = 0; + std::vector::const_iterator it = + composition_offset_table_.begin(); + for (; it != composition_offset_table_.end(); ++it) { + num_samples += it->sample_count; + } + return num_samples; +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/composition_offset_iterator.h b/media/mp4/composition_offset_iterator.h new file mode 100644 index 0000000000..9d55381353 --- /dev/null +++ b/media/mp4/composition_offset_iterator.h @@ -0,0 +1,54 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Implements a wrapper around Composition Time to Sample Box (CTTS) to iterate +// through the compressed table. This class also provides convenient functions +// to query total number of samples and the composition offset for a particular +// sample. + +#ifndef MEDIA_MP4_COMPOSITION_OFFSET_ITERATOR_H_ +#define MEDIA_MP4_COMPOSITION_OFFSET_ITERATOR_H_ + +#include + +#include "media/mp4/box_definitions.h" + +namespace media { +namespace mp4 { + +class CompositionOffsetIterator { + public: + explicit CompositionOffsetIterator( + const CompositionTimeToSample& composition_time_to_sample); + + // Advance the properties to refer to the next sample. Return status + // indicating whether the sample is still valid. + bool AdvanceSample(); + + // Return whether the current sample is valid. + bool IsValid(); + + // Return sample offset for current sample. + uint32 sample_offset() { + return iterator_->sample_offset; + } + + // Return sample offset @ sample, 1-based. + uint32 SampleOffset(uint32 sample); + + // Return total number of samples. + uint32 NumSamples(); + + private: + uint32 sample_index_; + const std::vector& composition_offset_table_; + std::vector::const_iterator iterator_; + + DISALLOW_COPY_AND_ASSIGN(CompositionOffsetIterator); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_COMPOSITION_OFFSET_ITERATOR_H_ diff --git a/media/mp4/composition_offset_iterator_unittest.cc b/media/mp4/composition_offset_iterator_unittest.cc new file mode 100644 index 0000000000..7637d0850d --- /dev/null +++ b/media/mp4/composition_offset_iterator_unittest.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/composition_offset_iterator.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace mp4 { + +const CompositionOffset kCompositionOffsets[] = + {{10, 8}, {9, 5}, {25, 7}, {48, 63}, {8, 2}}; + +class CompositionOffsetIteratorTest : public testing::Test { + public: + CompositionOffsetIteratorTest() { + // Build composition offset table from kCompositionOffsets. + uint32 composition_offset = 0; + uint32 length = sizeof(kCompositionOffsets) / sizeof(CompositionOffset); + for (uint32 i = 0; i < length; ++i) { + for (uint32 j = 0; j < kCompositionOffsets[i].sample_count; ++j) { + composition_offset_table_.push_back( + kCompositionOffsets[i].sample_offset); + } + } + + composition_time_to_sample_.composition_offset.assign( + kCompositionOffsets, kCompositionOffsets + length); + composition_offset_iterator_.reset( + new CompositionOffsetIterator(composition_time_to_sample_)); + } + + protected: + std::vector composition_offset_table_; + CompositionTimeToSample composition_time_to_sample_; + scoped_ptr composition_offset_iterator_; + + private: + DISALLOW_COPY_AND_ASSIGN(CompositionOffsetIteratorTest); +}; + +TEST_F(CompositionOffsetIteratorTest, EmptyCompositionTime) { + CompositionTimeToSample composition_time_to_sample; + CompositionOffsetIterator iterator(composition_time_to_sample); + EXPECT_FALSE(iterator.IsValid()); + EXPECT_EQ(0, iterator.NumSamples()); +} + +TEST_F(CompositionOffsetIteratorTest, NumSamples) { + ASSERT_EQ(composition_offset_table_.size(), + composition_offset_iterator_->NumSamples()); +} + +TEST_F(CompositionOffsetIteratorTest, AdvanceSample) { + ASSERT_EQ(composition_offset_table_[0], + composition_offset_iterator_->sample_offset()); + for (uint32 sample = 1; sample < composition_offset_table_.size(); ++sample) { + ASSERT_TRUE(composition_offset_iterator_->AdvanceSample()); + ASSERT_EQ(composition_offset_table_[sample], + composition_offset_iterator_->sample_offset()); + ASSERT_TRUE(composition_offset_iterator_->IsValid()); + } + ASSERT_FALSE(composition_offset_iterator_->AdvanceSample()); + ASSERT_FALSE(composition_offset_iterator_->IsValid()); +} + +TEST_F(CompositionOffsetIteratorTest, SampleOffset) { + for (uint32 sample = 0; sample < composition_offset_table_.size(); ++sample) { + ASSERT_EQ(composition_offset_table_[sample], + composition_offset_iterator_->SampleOffset(sample+1)); + } +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/decoding_time_iterator.cc b/media/mp4/decoding_time_iterator.cc new file mode 100644 index 0000000000..80e57f2ca9 --- /dev/null +++ b/media/mp4/decoding_time_iterator.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/decoding_time_iterator.h" + +#include + +#include "base/logging.h" + +namespace media { +namespace mp4 { + +DecodingTimeIterator::DecodingTimeIterator( + const DecodingTimeToSample& decoding_time_to_sample) + : sample_index_(0), + decoding_time_table_(decoding_time_to_sample.decoding_time), + iterator_(decoding_time_table_.begin()) { +} + +bool DecodingTimeIterator::AdvanceSample() { + ++sample_index_; + if (sample_index_ >= iterator_->sample_count) { + ++iterator_; + if (iterator_ == decoding_time_table_.end()) + return false; + sample_index_ = 0; + } + return true; +} + +bool DecodingTimeIterator::IsValid() { + return iterator_ != decoding_time_table_.end() + && sample_index_ < iterator_->sample_count; +} + +uint64 DecodingTimeIterator::Duration(uint32 start_sample, uint32 end_sample) { + DCHECK(start_sample <= end_sample); + uint32 current_sample = 0; + uint32 prev_sample = 0; + uint64 duration = 0; + std::vector::const_iterator it = decoding_time_table_.begin(); + for (; it != decoding_time_table_.end(); ++it) { + current_sample += it->sample_count; + if (current_sample >= start_sample) { + duration += (std::min(end_sample, current_sample) + - std::max(start_sample, prev_sample + 1) + 1) * it->sample_delta; + if (current_sample >= end_sample) + break; + } + prev_sample = current_sample; + } + return duration; +} + +uint32 DecodingTimeIterator::NumSamples() { + uint32 num_samples = 0; + std::vector::const_iterator it = decoding_time_table_.begin(); + for (; it != decoding_time_table_.end(); ++it) { + num_samples += it->sample_count; + } + return num_samples; +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/decoding_time_iterator.h b/media/mp4/decoding_time_iterator.h new file mode 100644 index 0000000000..3ac5ba9c57 --- /dev/null +++ b/media/mp4/decoding_time_iterator.h @@ -0,0 +1,54 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Implements a wrapper around Decoding Time to Sample Box (STTS) to iterate +// through the compressed table. This class also provides convenient functions +// to query total number of samples and the duration from start_sample to +// end_sample. + +#ifndef MEDIA_MP4_DECODING_TIME_ITERATOR_H_ +#define MEDIA_MP4_DECODING_TIME_ITERATOR_H_ + +#include + +#include "media/mp4/box_definitions.h" + +namespace media { +namespace mp4 { + +class DecodingTimeIterator { + public: + explicit DecodingTimeIterator( + const DecodingTimeToSample& decoding_time_to_sample); + + // Advance the properties to refer to the next sample. Return status + // indicating whether the sample is still valid. + bool AdvanceSample(); + + // Return whether the current sample is valid. + bool IsValid(); + + // Return sample delta for current sample. + uint32 sample_delta() { + return iterator_->sample_delta; + } + + // Return duration from start_sample to end_sample, both 1-based, inclusive. + uint64 Duration(uint32 start_sample, uint32 end_sample); + + // Return total number of samples in the table. + uint32 NumSamples(); + + private: + uint32 sample_index_; + const std::vector& decoding_time_table_; + std::vector::const_iterator iterator_; + + DISALLOW_COPY_AND_ASSIGN(DecodingTimeIterator); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_DECODING_TIME_ITERATOR_H_ diff --git a/media/mp4/decoding_time_iterator_unittest.cc b/media/mp4/decoding_time_iterator_unittest.cc new file mode 100644 index 0000000000..a37b2b6402 --- /dev/null +++ b/media/mp4/decoding_time_iterator_unittest.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/decoding_time_iterator.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace mp4 { + +const DecodingTime kDecodingTimes[] = + {{10, 8}, {9, 5}, {25, 7}, {48, 63}, {8, 2}}; + +class DecodingTimeIteratorTest : public testing::Test { + public: + DecodingTimeIteratorTest() { + // Build decoding time table from kDecodingTimes. + uint32 decoding_time = 0; + uint32 length = sizeof(kDecodingTimes) / sizeof(DecodingTime); + for (uint32 i = 0; i < length; ++i) { + for (uint32 j = 0; j < kDecodingTimes[i].sample_count; ++j) { + decoding_time += kDecodingTimes[i].sample_delta; + decoding_time_table_.push_back(decoding_time); + } + } + + decoding_time_to_sample_.decoding_time.assign(kDecodingTimes, + kDecodingTimes + length); + decoding_time_iterator_.reset( + new DecodingTimeIterator(decoding_time_to_sample_)); + } + + protected: + std::vector decoding_time_table_; + DecodingTimeToSample decoding_time_to_sample_; + scoped_ptr decoding_time_iterator_; + + private: + DISALLOW_COPY_AND_ASSIGN(DecodingTimeIteratorTest); +}; + +TEST_F(DecodingTimeIteratorTest, EmptyDecodingTime) { + DecodingTimeToSample decoding_time_to_sample; + DecodingTimeIterator iterator(decoding_time_to_sample); + EXPECT_FALSE(iterator.IsValid()); + EXPECT_EQ(0, iterator.NumSamples()); +} + +TEST_F(DecodingTimeIteratorTest, NumSamples) { + ASSERT_EQ(decoding_time_table_.size(), decoding_time_iterator_->NumSamples()); +} + +TEST_F(DecodingTimeIteratorTest, AdvanceSample) { + ASSERT_EQ(decoding_time_table_[0], decoding_time_iterator_->sample_delta()); + for (uint32 sample = 1; sample < decoding_time_table_.size(); ++sample) { + ASSERT_TRUE(decoding_time_iterator_->AdvanceSample()); + ASSERT_EQ(decoding_time_table_[sample] - decoding_time_table_[sample - 1], + decoding_time_iterator_->sample_delta()); + ASSERT_TRUE(decoding_time_iterator_->IsValid()); + } + ASSERT_FALSE(decoding_time_iterator_->AdvanceSample()); + ASSERT_FALSE(decoding_time_iterator_->IsValid()); +} + +TEST_F(DecodingTimeIteratorTest, Duration) { + for (uint32 i = 0; i < decoding_time_table_.size(); ++i) { + for (uint32 j = i; j < decoding_time_table_.size(); ++j) { + ASSERT_EQ( + decoding_time_table_[j] - (i == 0 ? 0 : decoding_time_table_[i - 1]), + decoding_time_iterator_->Duration(i + 1, j + 1)); + } + } +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/fourccs.h b/media/mp4/fourccs.h index b71d2ff3a5..982b6aa62e 100644 --- a/media/mp4/fourccs.h +++ b/media/mp4/fourccs.h @@ -69,6 +69,7 @@ enum FourCC { FOURCC_STSZ = 0x7374737a, FOURCC_STTS = 0x73747473, FOURCC_STYP = 0x73747970, + FOURCC_STZ2 = 0x73747a32, FOURCC_TENC = 0x74656e63, FOURCC_TFDT = 0x74666474, FOURCC_TFHD = 0x74666864, diff --git a/media/mp4/mp4_media_parser.cc b/media/mp4/mp4_media_parser.cc index ba3c93c4b4..489a9e0d36 100644 --- a/media/mp4/mp4_media_parser.cc +++ b/media/mp4/mp4_media_parser.cc @@ -114,15 +114,15 @@ bool MP4MediaParser::ParseBox(bool* err) { scoped_ptr reader(BoxReader::ReadTopLevelBox(buf, size, err)); if (reader.get() == NULL) return false; + // Set up mdat offset for ReadMDATsUntil(). + mdat_tail_ = queue_.head() + reader->size(); + if (reader->type() == FOURCC_MOOV) { *err = !ParseMoov(reader.get()); } else if (reader->type() == FOURCC_MOOF) { moof_head_ = queue_.head(); *err = !ParseMoof(reader.get()); - // Set up first mdat offset for ReadMDATsUntil(). - mdat_tail_ = queue_.head() + reader->size(); - // Return early to avoid evicting 'moof' data from queue. Auxiliary info may // be located anywhere in the file, including inside the 'moof' itself. // (Since 'default-base-is-moof' is mandated, no data references can come @@ -160,12 +160,22 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { // a codec reconfiguration for fragments using a sample description index // different from the previous one size_t desc_idx = 0; - for (size_t t = 0; t < moov_->extends.tracks.size(); t++) { - const TrackExtends& trex = moov_->extends.tracks[t]; - if (trex.track_id == track->header.track_id) { - desc_idx = trex.default_sample_description_index; - break; + + // Read sample description index from mvex if it exists otherwise read + // from the first entry in Sample To Chunk box. + if (moov_->extends.tracks.size() > 0) { + for (size_t t = 0; t < moov_->extends.tracks.size(); t++) { + const TrackExtends& trex = moov_->extends.tracks[t]; + if (trex.track_id == track->header.track_id) { + desc_idx = trex.default_sample_description_index; + break; + } } + } else { + const std::vector& chunk_info = + track->media.information.sample_table.sample_to_chunk.chunk_info; + RCHECK(chunk_info.size() > 0); + desc_idx = chunk_info[0].sample_description_index; } RCHECK(desc_idx > 0); desc_idx -= 1; // BMFF descriptor index is one-based @@ -287,15 +297,17 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { init_cb_.Run(true, streams); EmitNeedKeyIfNecessary(moov_->pssh); + runs_.reset(new TrackRunIterator(moov_.get())); + RCHECK(runs_->Init()); + ChangeState(kEmittingSamples); return true; } bool MP4MediaParser::ParseMoof(BoxReader* reader) { - RCHECK(moov_.get()); // Must already have initialization segment + // Must already have initialization segment. + RCHECK(moov_.get() && runs_.get()); MovieFragment moof; RCHECK(moof.Parse(reader)); - if (!runs_) - runs_.reset(new TrackRunIterator(moov_.get())); RCHECK(runs_->Init(moof)); EmitNeedKeyIfNecessary(moof.pssh); ChangeState(kEmittingSamples); diff --git a/media/mp4/mp4_media_parser_unittest.cc b/media/mp4/mp4_media_parser_unittest.cc index 03c12b5158..e58fd4fde8 100644 --- a/media/mp4/mp4_media_parser_unittest.cc +++ b/media/mp4/mp4_media_parser_unittest.cc @@ -117,12 +117,12 @@ TEST_F(MP4MediaParserTest, Reinitialization) { } TEST_F(MP4MediaParserTest, MPEG2_AAC_LC) { - parser_.reset(new MP4MediaParser()); ParseMP4File("bear-mpeg2-aac-only_frag.mp4", 512); } -// TODO(strobe): Create and test media which uses CENC auxiliary info stored -// inside a private box +TEST_F(MP4MediaParserTest, NON_FRAGMENTED_MP4) { + ParseMP4File("bear-1280x720.mp4", 512); +} } // namespace mp4 } // namespace media diff --git a/media/mp4/sync_sample_iterator.cc b/media/mp4/sync_sample_iterator.cc new file mode 100644 index 0000000000..7036a9698d --- /dev/null +++ b/media/mp4/sync_sample_iterator.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/sync_sample_iterator.h" + +#include + +namespace media { +namespace mp4 { + +SyncSampleIterator::SyncSampleIterator(const SyncSample& sync_sample) + : sample_number_(1), + sync_sample_vector_(sync_sample.sample_number), + iterator_(sync_sample_vector_.begin()), + is_empty_(iterator_ == sync_sample_vector_.end()) { +} + +bool SyncSampleIterator::AdvanceSample() { + if (iterator_ != sync_sample_vector_.end() && sample_number_ == *iterator_) + ++iterator_; + ++sample_number_; + return true; +} + +bool SyncSampleIterator::IsSyncSample() { + // If the sync sample box is not present, every sample is a sync sample. + if (is_empty_) + return true; + return iterator_ != sync_sample_vector_.end() && sample_number_ == *iterator_; +} + +bool SyncSampleIterator::IsSyncSample(uint32 sample) { + // If the sync sample box is not present, every sample is a sync sample. + if (is_empty_) + return true; + return std::binary_search(sync_sample_vector_.begin(), + sync_sample_vector_.end(), sample); +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/sync_sample_iterator.h b/media/mp4/sync_sample_iterator.h new file mode 100644 index 0000000000..3d44de434f --- /dev/null +++ b/media/mp4/sync_sample_iterator.h @@ -0,0 +1,45 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Implements a wrapper around Sync Sample Box (STSS) to iterate through the +// compressed table. + +#ifndef MEDIA_MP4_SYNC_SAMPLE_ITERATOR_H_ +#define MEDIA_MP4_SYNC_SAMPLE_ITERATOR_H_ + +#include + +#include "media/mp4/box_definitions.h" + +namespace media { +namespace mp4 { + +// Sample to Chunk Box (STSS) Iterator. +class SyncSampleIterator { + public: + explicit SyncSampleIterator(const SyncSample& sync_sample); + + // Advance the properties to refer to the next sample. Return status + // indicating whether the sample is still valid. + bool AdvanceSample(); + + // Return whether the current sample is a sync sample. + bool IsSyncSample(); + + // Return whether sample (1-based) is a sync sample. + bool IsSyncSample(uint32 sample); + + private: + uint32 sample_number_; + const std::vector& sync_sample_vector_; + std::vector::const_iterator iterator_; + bool is_empty_; + + DISALLOW_COPY_AND_ASSIGN(SyncSampleIterator); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_SYNC_SAMPLE_ITERATOR_H_ diff --git a/media/mp4/sync_sample_iterator_unittest.cc b/media/mp4/sync_sample_iterator_unittest.cc new file mode 100644 index 0000000000..36c428327b --- /dev/null +++ b/media/mp4/sync_sample_iterator_unittest.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/sync_sample_iterator.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +const uint32 kNumSamples = 100; +const uint32 kSyncSamples[] = {3, 10, 30, 35, 89, 97}; + +// Check if sample is an element in kSyncSamples. +bool InSyncSamples(uint32 sample) { + for (uint32 i = 0; i < sizeof(kSyncSamples) / sizeof(uint32); ++i) { + if (sample == kSyncSamples[i]) + return true; + } + return false; +} +} // namespace + +namespace media { +namespace mp4 { + +TEST(SyncSampleIteratorTest, EmptySyncSample) { + SyncSample sync_sample; + SyncSampleIterator iterator(sync_sample); + EXPECT_TRUE(iterator.IsSyncSample()); + EXPECT_TRUE(iterator.IsSyncSample(kNumSamples)); +} + +TEST(SyncSampleIteratorTest, SyncSample) { + SyncSample sync_sample; + sync_sample.sample_number.assign( + kSyncSamples, kSyncSamples + sizeof(kSyncSamples) / sizeof(uint32)); + SyncSampleIterator iterator(sync_sample); + + uint32 i = 1; + + // Check if it is sync sample using SyncSampleIterator::AdvanceSample() and + // SyncSampleIterator::IsSyncSample(). + for (; i <= kNumSamples / 2; ++i) { + ASSERT_EQ(InSyncSamples(i), iterator.IsSyncSample()); + ASSERT_TRUE(iterator.AdvanceSample()); + } + + // Check if it is sync sample using SyncSampleIterator::IsSyncSample(uint32). + // No need to advance sample for this case. + for (; i <= kNumSamples / 2; ++i) { + ASSERT_EQ(InSyncSamples(i), iterator.IsSyncSample(i)); + } +} + +} // namespace mp4 +} // namespace media diff --git a/media/mp4/track_run_iterator.cc b/media/mp4/track_run_iterator.cc index d547677c71..2dd63a2e26 100644 --- a/media/mp4/track_run_iterator.cc +++ b/media/mp4/track_run_iterator.cc @@ -6,7 +6,11 @@ #include +#include "media/mp4/chunk_info_iterator.h" +#include "media/mp4/composition_offset_iterator.h" +#include "media/mp4/decoding_time_iterator.h" #include "media/mp4/rcheck.h" +#include "media/mp4/sync_sample_iterator.h" namespace { static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; @@ -49,8 +53,8 @@ TrackRunInfo::TrackRunInfo() sample_start_offset(-1), is_audio(false), aux_info_start_offset(-1), - aux_info_default_size(-1), - aux_info_total_size(-1) { + aux_info_default_size(0), + aux_info_total_size(0) { } TrackRunInfo::~TrackRunInfo() {} @@ -128,6 +132,144 @@ class CompareMinTrackRunDataOffset { } }; +bool TrackRunIterator::Init() { + runs_.clear(); + + for (std::vector::const_iterator trak = moov_->tracks.begin(); + trak != moov_->tracks.end(); ++trak) { + const SampleDescription& stsd = + trak->media.information.sample_table.description; + if (stsd.type != kAudio && stsd.type != kVideo) { + DVLOG(1) << "Skipping unhandled track type"; + continue; + } + + // Process edit list to remove CTS offset introduced in the presence of + // B-frames (those that contain a single edit with a nonnegative media + // time). Other uses of edit lists are not supported, as they are + // both uncommon and better served by higher-level protocols. + int64 edit_list_offset = 0; + const std::vector& edits = trak->edit.list.edits; + if (!edits.empty()) { + if (edits.size() > 1) + DVLOG(1) << "Multi-entry edit box detected; some components ignored."; + + if (edits[0].media_time < 0) { + DVLOG(1) << "Empty edit list entry ignored."; + } else { + edit_list_offset = -edits[0].media_time; + } + } + + DecodingTimeIterator decoding_time( + trak->media.information.sample_table.decoding_time_to_sample); + CompositionOffsetIterator composition_offset( + trak->media.information.sample_table.composition_time_to_sample); + bool has_composition_offset = composition_offset.IsValid(); + ChunkInfoIterator chunk_info( + trak->media.information.sample_table.sample_to_chunk); + SyncSampleIterator sync_sample( + trak->media.information.sample_table.sync_sample); + // Skip processing saiz and saio boxes for non-fragmented mp4 as we + // don't support encrypted non-fragmented mp4. + + const SampleSize& sample_size = + trak->media.information.sample_table.sample_size; + const std::vector& chunk_offset_vector = + trak->media.information.sample_table.chunk_offset.offsets; + + int64 run_start_dts = 0; + int64 run_data_offset = 0; + + uint32 num_samples = sample_size.sample_count; + uint32 num_chunks = chunk_offset_vector.size(); + + // Check that total number of samples match. + DCHECK(num_samples == decoding_time.NumSamples() && + num_samples == composition_offset.NumSamples() && + (num_chunks == 0 || + num_samples == chunk_info.NumSamples(1, num_chunks)) && + num_chunks >= chunk_info.LastFirstChunk()); + + if (num_samples > 0) { + // Verify relevant tables are not empty. + RCHECK(decoding_time.IsValid() && + composition_offset.IsValid() && + chunk_info.IsValid()); + } + + uint32 sample_index = 0; + for (uint32 chunk_index = 0; chunk_index < num_chunks; ++chunk_index) { + RCHECK(chunk_info.current_chunk() == chunk_index + 1); + + TrackRunInfo tri; + tri.track_id = trak->header.track_id; + tri.timescale = trak->media.header.timescale; + tri.start_dts = run_start_dts; + tri.sample_start_offset = chunk_offset_vector[chunk_index]; + + uint32 desc_idx = chunk_info.sample_description_index(); + RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file. + desc_idx -= 1; + + tri.is_audio = (stsd.type == kAudio); + if (tri.is_audio) { + RCHECK(!stsd.audio_entries.empty()); + if (desc_idx > stsd.audio_entries.size()) + desc_idx = 0; + tri.audio_description = &stsd.audio_entries[desc_idx]; + // We don't support encrypted non-fragmented mp4 for now. + RCHECK(!tri.audio_description->sinf.info.track_encryption.is_encrypted); + } else { + RCHECK(!stsd.video_entries.empty()); + if (desc_idx > stsd.video_entries.size()) + desc_idx = 0; + tri.video_description = &stsd.video_entries[desc_idx]; + // We don't support encrypted non-fragmented mp4 for now. + RCHECK(!tri.video_description->sinf.info.track_encryption.is_encrypted); + } + + uint32 samples_per_chunk = chunk_info.samples_per_chunk(); + tri.samples.resize(samples_per_chunk); + for (uint32 k = 0; k < samples_per_chunk; ++k) { + SampleInfo& sample = tri.samples[k]; + sample.size = + sample_size.sample_size != 0 ? + sample_size.sample_size : sample_size.sizes[sample_index]; + sample.duration = decoding_time.sample_delta(); + sample.cts_offset = + has_composition_offset ? composition_offset.sample_offset() : 0; + sample.cts_offset += edit_list_offset; + sample.is_keyframe = sync_sample.IsSyncSample(); + + run_start_dts += sample.duration; + + // Advance to next sample. Should success except for last sample. + ++sample_index; + RCHECK(chunk_info.AdvanceSample() && sync_sample.AdvanceSample()); + if (sample_index == num_samples) { + // We should hit end of tables for decoding time and composition + // offset. + RCHECK(!decoding_time.AdvanceSample()); + if (has_composition_offset) + RCHECK(!composition_offset.AdvanceSample()); + } else { + RCHECK(decoding_time.AdvanceSample()); + if (has_composition_offset) + RCHECK(composition_offset.AdvanceSample()); + } + } + + runs_.push_back(tri); + } + } + + std::sort(runs_.begin(), runs_.end(), CompareMinTrackRunDataOffset()); + run_itr_ = runs_.begin(); + ResetRun(); + return true; +} + bool TrackRunIterator::Init(const MovieFragment& moof) { runs_.clear(); diff --git a/media/mp4/track_run_iterator.h b/media/mp4/track_run_iterator.h index 0ab1e32ebb..94a06a781e 100644 --- a/media/mp4/track_run_iterator.h +++ b/media/mp4/track_run_iterator.h @@ -27,6 +27,13 @@ class TrackRunIterator { explicit TrackRunIterator(const Movie* moov); ~TrackRunIterator(); + // For non-fragmented mp4, moov contains all the chunks information; This + // function sets up the iterator to handle all the chunks. + // For fragmented mp4, chunk and sample information are generally contained + // in moof. This function is a no-op in this case. Init(moof) will be called + // later after parsing moof. + bool Init(); + // Sets up the iterator to handle all the runs from the current fragment. bool Init(const MovieFragment& moof);