Implemented non-fragmented mp4 parsing support.

Change-Id: I956704d1bf7d2d018ad65343b462c62b5042aba6
This commit is contained in:
Kongqun Yang 2013-10-08 10:37:58 -07:00
parent 338e3342b0
commit db7602f42e
21 changed files with 1238 additions and 23 deletions

View File

@ -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);
}

View File

@ -209,14 +209,95 @@ struct SampleDescription : Box {
std::vector<AudioSampleEntry> audio_entries;
};
struct DecodingTime {
uint32 sample_count;
uint32 sample_delta;
};
// stts.
struct DecodingTimeToSample : Box {
DECLARE_BOX_METHODS(DecodingTimeToSample);
std::vector<DecodingTime> 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<CompositionOffset> 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<ChunkInfo> chunk_info;
};
// stsz.
struct SampleSize : Box {
DECLARE_BOX_METHODS(SampleSize);
uint32 sample_size;
uint32 sample_count;
std::vector<uint32> 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<uint64> offsets;
};
// co64.
struct ChunkLargeOffset : ChunkOffset {
DECLARE_BOX_METHODS(ChunkLargeOffset);
};
// stss. Optional.
struct SyncSample : Box {
DECLARE_BOX_METHODS(SyncSample);
std::vector<uint32> 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 {

View File

@ -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);

View File

@ -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;

View File

@ -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 <algorithm>
#include <limits>
#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<ChunkInfo>::const_iterator it = chunk_info_table_.begin();
it != chunk_info_table_.end(); ++it) {
last_chunk = (
(it + 1 == chunk_info_table_.end()) ?
std::numeric_limits<uint32>::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

View File

@ -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 <vector>
#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<ChunkInfo>& chunk_info_table_;
std::vector<ChunkInfo>::const_iterator iterator_;
DISALLOW_COPY_AND_ASSIGN(ChunkInfoIterator);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_CHUNK_INFO_ITERATOR_H_

View File

@ -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<ChunkProperty> chunk_info_table_;
SampleToChunk sample_to_chunk_;
scoped_ptr<ChunkInfoIterator> 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

View File

@ -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<CompositionOffset>::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<CompositionOffset>::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

View File

@ -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 <vector>
#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<CompositionOffset>& composition_offset_table_;
std::vector<CompositionOffset>::const_iterator iterator_;
DISALLOW_COPY_AND_ASSIGN(CompositionOffsetIterator);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_COMPOSITION_OFFSET_ITERATOR_H_

View File

@ -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<uint32> composition_offset_table_;
CompositionTimeToSample composition_time_to_sample_;
scoped_ptr<CompositionOffsetIterator> 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

View File

@ -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 <algorithm>
#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<DecodingTime>::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<DecodingTime>::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

View File

@ -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 <vector>
#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<DecodingTime>& decoding_time_table_;
std::vector<DecodingTime>::const_iterator iterator_;
DISALLOW_COPY_AND_ASSIGN(DecodingTimeIterator);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_DECODING_TIME_ITERATOR_H_

View File

@ -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<uint32> decoding_time_table_;
DecodingTimeToSample decoding_time_to_sample_;
scoped_ptr<DecodingTimeIterator> 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

View File

@ -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,

View File

@ -114,15 +114,15 @@ bool MP4MediaParser::ParseBox(bool* err) {
scoped_ptr<BoxReader> 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<ChunkInfo>& 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);

View File

@ -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

View File

@ -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 <algorithm>
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

View File

@ -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 <vector>
#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<uint32>& sync_sample_vector_;
std::vector<uint32>::const_iterator iterator_;
bool is_empty_;
DISALLOW_COPY_AND_ASSIGN(SyncSampleIterator);
};
} // namespace mp4
} // namespace media
#endif // MEDIA_MP4_SYNC_SAMPLE_ITERATOR_H_

View File

@ -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

View File

@ -6,7 +6,11 @@
#include <algorithm>
#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<Track>::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<EditListEntry>& 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<uint64>& 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();

View File

@ -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);