Implemented non-fragmented mp4 parsing support.
Change-Id: I956704d1bf7d2d018ad65343b462c62b5042aba6
This commit is contained in:
parent
338e3342b0
commit
db7602f42e
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue