summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/filters/chunk_demuxer.cc23
-rw-r--r--media/filters/ffmpeg_audio_decoder.cc4
-rw-r--r--media/filters/ffmpeg_video_decoder.cc4
-rw-r--r--media/media.gyp19
-rw-r--r--media/mp4/avc.cc114
-rw-r--r--media/mp4/avc.h34
-rw-r--r--media/mp4/avc_unittest.cc96
-rw-r--r--media/mp4/box_definitions.cc638
-rw-r--r--media/mp4/box_definitions.h330
-rw-r--r--media/mp4/box_reader.cc193
-rw-r--r--media/mp4/box_reader.h201
-rw-r--r--media/mp4/box_reader_unittest.cc182
-rw-r--r--media/mp4/cenc.cc45
-rw-r--r--media/mp4/cenc.h36
-rw-r--r--media/mp4/fourccs.h98
-rw-r--r--media/mp4/mp4_stream_parser.cc341
-rw-r--r--media/mp4/mp4_stream_parser.h97
-rw-r--r--media/mp4/mp4_stream_parser_unittest.cc114
-rw-r--r--media/mp4/offset_byte_queue.cc63
-rw-r--r--media/mp4/offset_byte_queue.h66
-rw-r--r--media/mp4/offset_byte_queue_unittest.cc96
-rw-r--r--media/mp4/rcheck.h18
-rw-r--r--media/mp4/track_run_iterator.cc263
-rw-r--r--media/mp4/track_run_iterator.h118
24 files changed, 3193 insertions, 0 deletions
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 2d47cc2..fcb3625 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -11,6 +11,7 @@
#include "media/base/stream_parser_buffer.h"
#include "media/base/video_decoder_config.h"
#include "media/filters/chunk_demuxer_client.h"
+#include "media/mp4/mp4_stream_parser.h"
#include "media/webm/webm_stream_parser.h"
namespace media {
@@ -31,6 +32,11 @@ struct SupportedTypeInfo {
static const CodecInfo kVP8CodecInfo = { "vp8", DemuxerStream::VIDEO };
static const CodecInfo kVorbisCodecInfo = { "vorbis", DemuxerStream::AUDIO };
+// TODO(strobe): Perform matching against supported profiles and levels, or
+// simply accept codecs that match a prefix.
+static const CodecInfo kH264CodecInfo = { "avc1.4D4041", DemuxerStream::VIDEO };
+static const CodecInfo kAACCodecInfo = { "mp4a.40.2", DemuxerStream::AUDIO };
+
static const CodecInfo* kVideoWebMCodecs[] = {
&kVP8CodecInfo,
&kVorbisCodecInfo,
@@ -42,13 +48,30 @@ static const CodecInfo* kAudioWebMCodecs[] = {
NULL
};
+static const CodecInfo* kVideoMP4Codecs[] = {
+ &kH264CodecInfo,
+ &kAACCodecInfo,
+ NULL
+};
+
+static const CodecInfo* kAudioMP4Codecs[] = {
+ &kAACCodecInfo,
+ NULL
+};
+
static StreamParser* BuildWebMParser() {
return new WebMStreamParser();
}
+static StreamParser* BuildMP4Parser() {
+ return new mp4::MP4StreamParser();
+}
+
static const SupportedTypeInfo kSupportedTypeInfo[] = {
{ "video/webm", &BuildWebMParser, kVideoWebMCodecs },
{ "audio/webm", &BuildWebMParser, kAudioWebMCodecs },
+ { "video/mp4", &BuildMP4Parser, kVideoMP4Codecs },
+ { "audio/mp4", &BuildMP4Parser, kAudioMP4Codecs },
};
// Checks to see if the specified |type| and |codecs| list are supported.
diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc
index f48c0fa..58f3dcd 100644
--- a/media/filters/ffmpeg_audio_decoder.cc
+++ b/media/filters/ffmpeg_audio_decoder.cc
@@ -11,6 +11,7 @@
#include "media/base/demuxer.h"
#include "media/base/pipeline.h"
#include "media/ffmpeg/ffmpeg_common.h"
+#include "media/filters/ffmpeg_glue.h"
namespace media {
@@ -49,6 +50,9 @@ void FFmpegAudioDecoder::Initialize(
const scoped_refptr<DemuxerStream>& stream,
const PipelineStatusCB& status_cb,
const StatisticsCB& statistics_cb) {
+ // Ensure FFmpeg has been initialized
+ FFmpegGlue::GetInstance();
+
if (!message_loop_) {
message_loop_ = message_loop_factory_cb_.Run();
message_loop_factory_cb_.Reset();
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 41bd0e4..c8e512b 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -18,6 +18,7 @@
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/ffmpeg/ffmpeg_common.h"
+#include "media/filters/ffmpeg_glue.h"
namespace media {
@@ -64,6 +65,9 @@ FFmpegVideoDecoder::FFmpegVideoDecoder(
void FFmpegVideoDecoder::Initialize(const scoped_refptr<DemuxerStream>& stream,
const PipelineStatusCB& status_cb,
const StatisticsCB& statistics_cb) {
+ // Ensure FFmpeg has been initialized
+ FFmpegGlue::GetInstance();
+
if (!message_loop_) {
message_loop_ = message_loop_factory_cb_.Run();
message_loop_factory_cb_.Reset();
diff --git a/media/media.gyp b/media/media.gyp
index 056407f..22c6ca0 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -235,6 +235,20 @@
'filters/video_frame_generator.h',
'filters/video_renderer_base.cc',
'filters/video_renderer_base.h',
+ 'mp4/avc.cc',
+ 'mp4/avc.h',
+ 'mp4/box_definitions.cc',
+ 'mp4/box_definitions.h',
+ 'mp4/box_reader.cc',
+ 'mp4/box_reader.h',
+ 'mp4/cenc.cc',
+ 'mp4/cenc.h',
+ 'mp4/mp4_stream_parser.cc',
+ 'mp4/mp4_stream_parser.h',
+ 'mp4/offset_byte_queue.cc',
+ 'mp4/offset_byte_queue.h',
+ 'mp4/track_run_iterator.cc',
+ 'mp4/track_run_iterator.h',
'video/capture/fake_video_capture_device.cc',
'video/capture/fake_video_capture_device.h',
'video/capture/linux/video_capture_device_linux.cc',
@@ -673,6 +687,10 @@
'filters/pipeline_integration_test_base.cc',
'filters/source_buffer_stream_unittest.cc',
'filters/video_renderer_base_unittest.cc',
+ 'mp4/avc_unittest.cc',
+ 'mp4/box_reader_unittest.cc',
+ 'mp4/mp4_stream_parser_unittest.cc',
+ 'mp4/offset_byte_queue_unittest.cc',
'video/capture/video_capture_device_unittest.cc',
'webm/cluster_builder.cc',
'webm/cluster_builder.h',
@@ -710,6 +728,7 @@
'filters/ffmpeg_video_decoder_unittest.cc',
'filters/pipeline_integration_test.cc',
'filters/pipeline_integration_test_base.cc',
+ 'mp4/mp4_stream_parser_unittest.cc',
'webm/webm_cluster_parser_unittest.cc',
],
}],
diff --git a/media/mp4/avc.cc b/media/mp4/avc.cc
new file mode 100644
index 0000000..faf9939
--- /dev/null
+++ b/media/mp4/avc.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 The Chromium Authors. 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/avc.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "media/mp4/box_definitions.h"
+#include "media/mp4/box_reader.h"
+
+namespace media {
+namespace mp4 {
+
+static const uint8 kAnnexBStartCode[] = {0, 0, 0, 1};
+static const int kAnnexBStartCodeSize = 4;
+
+static bool ConvertAVCToAnnexBInPlaceForLengthSize4(std::vector<uint8>* buf) {
+ const int kLengthSize = 4;
+ size_t pos = 0;
+ while (pos + kLengthSize < buf->size()) {
+ int nal_size = (*buf)[pos];
+ nal_size = (nal_size << 8) + (*buf)[pos+1];
+ nal_size = (nal_size << 8) + (*buf)[pos+2];
+ nal_size = (nal_size << 8) + (*buf)[pos+3];
+ std::copy(kAnnexBStartCode, kAnnexBStartCode + kAnnexBStartCodeSize,
+ buf->begin() + pos);
+ pos += kLengthSize + nal_size;
+ }
+ return pos == buf->size();
+}
+
+// static
+bool AVC::ConvertToAnnexB(int length_size, std::vector<uint8>* buffer) {
+ RCHECK(length_size == 1 || length_size == 2 || length_size == 4);
+
+ if (length_size == 4)
+ return ConvertAVCToAnnexBInPlaceForLengthSize4(buffer);
+
+ std::vector<uint8> temp;
+ temp.swap(*buffer);
+ buffer->reserve(temp.size() + 32);
+
+ size_t pos = 0;
+ while (pos + length_size < temp.size()) {
+ int nal_size = temp[pos];
+ if (length_size == 2) nal_size = (nal_size << 8) + temp[pos+1];
+ pos += length_size;
+
+ RCHECK(pos + nal_size <= temp.size());
+ buffer->insert(buffer->end(), kAnnexBStartCode,
+ kAnnexBStartCode + kAnnexBStartCodeSize);
+ buffer->insert(buffer->end(), temp.begin() + pos,
+ temp.begin() + pos + nal_size);
+ pos += nal_size;
+ }
+ return pos == temp.size();
+}
+
+// static
+bool AVC::InsertParameterSets(const AVCDecoderConfigurationRecord& avc_config,
+ std::vector<uint8>* buffer) {
+ int total_size = 0;
+ for (size_t i = 0; i < avc_config.sps_list.size(); i++)
+ total_size += avc_config.sps_list[i].size() + kAnnexBStartCodeSize;
+ for (size_t i = 0; i < avc_config.pps_list.size(); i++)
+ total_size += avc_config.pps_list[i].size() + kAnnexBStartCodeSize;
+
+ std::vector<uint8> temp;
+ temp.reserve(total_size);
+
+ for (size_t i = 0; i < avc_config.sps_list.size(); i++) {
+ temp.insert(temp.end(), kAnnexBStartCode,
+ kAnnexBStartCode + kAnnexBStartCodeSize);
+ temp.insert(temp.end(), avc_config.sps_list[i].begin(),
+ avc_config.sps_list[i].end());
+ }
+
+ for (size_t i = 0; i < avc_config.pps_list.size(); i++) {
+ temp.insert(temp.end(), kAnnexBStartCode,
+ kAnnexBStartCode + kAnnexBStartCodeSize);
+ temp.insert(temp.end(), avc_config.pps_list[i].begin(),
+ avc_config.pps_list[i].end());
+ }
+
+ buffer->insert(buffer->begin(), temp.begin(), temp.end());
+ return true;
+}
+
+// static
+ChannelLayout AVC::ConvertAACChannelCountToChannelLayout(int count) {
+ switch (count) {
+ case 1:
+ return CHANNEL_LAYOUT_MONO;
+ case 2:
+ return CHANNEL_LAYOUT_STEREO;
+ case 3:
+ return CHANNEL_LAYOUT_SURROUND;
+ case 4:
+ return CHANNEL_LAYOUT_4_0;
+ case 5:
+ return CHANNEL_LAYOUT_5_0;
+ case 6:
+ return CHANNEL_LAYOUT_5_1;
+ case 8:
+ return CHANNEL_LAYOUT_7_1;
+ default:
+ return CHANNEL_LAYOUT_UNSUPPORTED;
+ }
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/avc.h b/media/mp4/avc.h
new file mode 100644
index 0000000..2767c14
--- /dev/null
+++ b/media/mp4/avc.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_AVC_H_
+#define MEDIA_MP4_AVC_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/channel_layout.h"
+#include "media/base/media_export.h"
+
+namespace media {
+namespace mp4 {
+
+struct AVCDecoderConfigurationRecord;
+
+class MEDIA_EXPORT AVC {
+ public:
+ static bool ConvertToAnnexB(int length_size, std::vector<uint8>* buffer);
+
+ static bool InsertParameterSets(
+ const AVCDecoderConfigurationRecord& avc_config,
+ std::vector<uint8>* buffer);
+
+ static ChannelLayout ConvertAACChannelCountToChannelLayout(int count);
+};
+
+
+} // namespace mp4
+} // namespace media
+
+#endif // MEDIA_MP4_AVC_H_
diff --git a/media/mp4/avc_unittest.cc b/media/mp4/avc_unittest.cc
new file mode 100644
index 0000000..cdcc413
--- /dev/null
+++ b/media/mp4/avc_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+
+#include "base/basictypes.h"
+#include "media/base/stream_parser_buffer.h"
+#include "media/mp4/avc.h"
+#include "media/mp4/box_definitions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest/include/gtest/gtest-param-test.h"
+
+namespace media {
+namespace mp4 {
+
+static const uint8 kNALU1[] = { 0x01, 0x02, 0x03 };
+static const uint8 kNALU2[] = { 0x04, 0x05, 0x06, 0x07 };
+static const uint8 kExpected[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03,
+ 0x00, 0x00, 0x00, 0x01, 0x04, 0x05, 0x06, 0x07 };
+
+static const uint8 kExpectedParamSets[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x12,
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x34,
+ 0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78, 0x9a};
+
+class AVCConversionTest : public testing::TestWithParam<int> {
+ protected:
+ void MakeInputForLength(int length_size, std::vector<uint8>* buf) {
+ buf->clear();
+ for (int i = 1; i < length_size; i++)
+ buf->push_back(0);
+ buf->push_back(sizeof(kNALU1));
+ buf->insert(buf->end(), kNALU1, kNALU1 + sizeof(kNALU1));
+
+ for (int i = 1; i < length_size; i++)
+ buf->push_back(0);
+ buf->push_back(sizeof(kNALU2));
+ buf->insert(buf->end(), kNALU2, kNALU2 + sizeof(kNALU2));
+ }
+};
+
+TEST_P(AVCConversionTest, ParseCorrectly) {
+ std::vector<uint8> buf;
+ MakeInputForLength(GetParam(), &buf);
+ EXPECT_TRUE(AVC::ConvertToAnnexB(GetParam(), &buf));
+ EXPECT_EQ(buf.size(), sizeof(kExpected));
+ EXPECT_EQ(0, memcmp(kExpected, &buf[0], sizeof(kExpected)));
+}
+
+TEST_P(AVCConversionTest, ParsePartial) {
+ std::vector<uint8> buf;
+ MakeInputForLength(GetParam(), &buf);
+ buf.pop_back();
+ EXPECT_FALSE(AVC::ConvertToAnnexB(GetParam(), &buf));
+ // This tests a buffer ending in the middle of a NAL length. For length size
+ // of one, this can't happen, so we skip that case.
+ if (GetParam() != 1) {
+ MakeInputForLength(GetParam(), &buf);
+ buf.erase(buf.end() - (sizeof(kNALU2) + 1), buf.end());
+ EXPECT_FALSE(AVC::ConvertToAnnexB(GetParam(), &buf));
+ }
+}
+
+TEST_P(AVCConversionTest, ParseEmpty) {
+ std::vector<uint8> buf;
+ EXPECT_TRUE(AVC::ConvertToAnnexB(GetParam(), &buf));
+ EXPECT_EQ(0u, buf.size());
+}
+
+INSTANTIATE_TEST_CASE_P(AVCConversionTestValues,
+ AVCConversionTest,
+ ::testing::Values(1, 2, 4));
+
+TEST(AVC, InsertParameterSetsTest) {
+ AVCDecoderConfigurationRecord avc_config;
+ avc_config.sps_list.resize(2);
+ avc_config.sps_list[0].push_back(0x67);
+ avc_config.sps_list[0].push_back(0x12);
+ avc_config.sps_list[1].push_back(0x67);
+ avc_config.sps_list[1].push_back(0x34);
+ avc_config.pps_list.resize(1);
+ avc_config.pps_list[0].push_back(0x68);
+ avc_config.pps_list[0].push_back(0x56);
+ avc_config.pps_list[0].push_back(0x78);
+
+ std::vector<uint8> buf;
+ buf.push_back(0x9a);
+ EXPECT_TRUE(AVC::InsertParameterSets(avc_config, &buf));
+ EXPECT_EQ(0, memcmp(kExpectedParamSets, &buf[0],
+ sizeof(kExpectedParamSets)));
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/box_definitions.cc b/media/mp4/box_definitions.cc
new file mode 100644
index 0000000..e59d050
--- /dev/null
+++ b/media/mp4/box_definitions.cc
@@ -0,0 +1,638 @@
+// Copyright (c) 2012 The Chromium Authors. 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/box_definitions.h"
+
+#include "base/logging.h"
+#include "media/mp4/box_reader.h"
+#include "media/mp4/fourccs.h"
+#include "media/mp4/rcheck.h"
+
+namespace media {
+namespace mp4 {
+
+bool FileType::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFourCC(&major_brand) && reader->Read4(&minor_version));
+ size_t num_brands = (reader->size() - reader->pos()) / sizeof(FourCC);
+ return reader->SkipBytes(sizeof(FourCC) * num_brands); // compatible_brands
+}
+
+ProtectionSystemSpecificHeader::ProtectionSystemSpecificHeader() {}
+ProtectionSystemSpecificHeader::~ProtectionSystemSpecificHeader() {}
+FourCC ProtectionSystemSpecificHeader::BoxType() const { return FOURCC_PSSH; }
+
+bool ProtectionSystemSpecificHeader::Parse(BoxReader* reader) {
+ uint32 size;
+ return reader->SkipBytes(4) &&
+ reader->ReadVec(&system_id, 16) &&
+ reader->Read4(&size) &&
+ reader->ReadVec(&data, size);
+}
+
+SampleAuxiliaryInformationOffset::SampleAuxiliaryInformationOffset() {}
+SampleAuxiliaryInformationOffset::~SampleAuxiliaryInformationOffset() {}
+FourCC SampleAuxiliaryInformationOffset::BoxType() const { return FOURCC_SAIO; }
+
+bool SampleAuxiliaryInformationOffset::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader());
+ if (reader->flags() & 1)
+ RCHECK(reader->SkipBytes(8));
+
+ uint32 count;
+ RCHECK(reader->Read4(&count) &&
+ reader->HasBytes(count * (reader->version() == 1 ? 8 : 4)));
+ offsets.resize(count);
+
+ for (uint32 i = 0; i < count; i++) {
+ if (reader->version() == 1) {
+ RCHECK(reader->Read8(&offsets[i]));
+ } else {
+ RCHECK(reader->Read4Into8(&offsets[i]));
+ }
+ }
+ return true;
+}
+
+SampleAuxiliaryInformationSize::SampleAuxiliaryInformationSize()
+ : default_sample_info_size(0), sample_count(0) {
+}
+SampleAuxiliaryInformationSize::~SampleAuxiliaryInformationSize() {}
+FourCC SampleAuxiliaryInformationSize::BoxType() const { return FOURCC_SAIZ; }
+
+bool SampleAuxiliaryInformationSize::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader());
+ if (reader->flags() & 1)
+ RCHECK(reader->SkipBytes(8));
+
+ RCHECK(reader->Read1(&default_sample_info_size) &&
+ reader->Read4(&sample_count));
+ if (default_sample_info_size == 0)
+ return reader->ReadVec(&sample_info_sizes, sample_count);
+ return true;
+}
+
+OriginalFormat::OriginalFormat() {}
+OriginalFormat::~OriginalFormat() {}
+FourCC OriginalFormat::BoxType() const { return FOURCC_FRMA; }
+
+bool OriginalFormat::Parse(BoxReader* reader) {
+ return reader->ReadFourCC(&format);
+}
+
+SchemeType::SchemeType() {}
+SchemeType::~SchemeType() {}
+FourCC SchemeType::BoxType() const { return FOURCC_SCHM; }
+
+bool SchemeType::Parse(BoxReader* reader) {
+ RCHECK(reader->SkipBytes(4) &&
+ reader->ReadFourCC(&type) &&
+ reader->Read4(&version));
+ RCHECK(type == FOURCC_CENC);
+ return true;
+}
+
+TrackEncryption::TrackEncryption()
+ : is_encrypted(false), default_iv_size(0) {
+}
+TrackEncryption::~TrackEncryption() {}
+FourCC TrackEncryption::BoxType() const { return FOURCC_TENC; }
+
+bool TrackEncryption::Parse(BoxReader* reader) {
+ uint8 flag;
+ RCHECK(reader->SkipBytes(2) &&
+ reader->Read1(&flag) &&
+ reader->Read1(&default_iv_size) &&
+ reader->ReadVec(&default_kid, 16));
+ is_encrypted = (flag != 0);
+ if (is_encrypted) {
+ RCHECK(default_iv_size == 8 || default_iv_size == 16);
+ } else {
+ RCHECK(default_iv_size == 0);
+ }
+ return true;
+}
+
+SchemeInfo::SchemeInfo() {}
+SchemeInfo::~SchemeInfo() {}
+FourCC SchemeInfo::BoxType() const { return FOURCC_SCHI; }
+
+bool SchemeInfo::Parse(BoxReader* reader) {
+ return reader->ScanChildren() && reader->ReadChild(&track_encryption);
+}
+
+ProtectionSchemeInfo::ProtectionSchemeInfo() {}
+ProtectionSchemeInfo::~ProtectionSchemeInfo() {}
+FourCC ProtectionSchemeInfo::BoxType() const { return FOURCC_SINF; }
+
+bool ProtectionSchemeInfo::Parse(BoxReader* reader) {
+ return reader->ScanChildren() &&
+ reader->ReadChild(&type) &&
+ reader->ReadChild(&info);
+}
+
+MovieHeader::MovieHeader() {}
+MovieHeader::~MovieHeader() {}
+FourCC MovieHeader::BoxType() const { return FOURCC_MVHD; }
+
+bool MovieHeader::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader());
+
+ if (reader->version() == 1) {
+ RCHECK(reader->Read8(&creation_time) &&
+ reader->Read8(&modification_time) &&
+ reader->Read4(&timescale) &&
+ reader->Read8(&duration));
+ } else {
+ RCHECK(reader->Read4Into8(&creation_time) &&
+ reader->Read4Into8(&modification_time) &&
+ reader->Read4(&timescale) &&
+ reader->Read4Into8(&duration));
+ }
+
+ RCHECK(reader->Read4s(&rate) &&
+ reader->Read2s(&volume) &&
+ reader->SkipBytes(10) && // reserved
+ reader->SkipBytes(36) && // matrix
+ reader->SkipBytes(24) && // predefined zero
+ reader->Read4(&next_track_id));
+ return true;
+}
+
+TrackHeader::TrackHeader() {}
+TrackHeader::~TrackHeader() {}
+FourCC TrackHeader::BoxType() const { return FOURCC_TKHD; }
+
+bool TrackHeader::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader());
+ if (reader->version() == 1) {
+ RCHECK(reader->Read8(&creation_time) &&
+ reader->Read8(&modification_time) &&
+ reader->Read4(&track_id) &&
+ reader->SkipBytes(4) && // reserved
+ reader->Read8(&duration));
+ } else {
+ RCHECK(reader->Read4Into8(&creation_time) &&
+ reader->Read4Into8(&modification_time) &&
+ reader->Read4(&track_id) &&
+ reader->SkipBytes(4) && // reserved
+ reader->Read4Into8(&duration));
+ }
+
+ RCHECK(reader->SkipBytes(8) && // reserved
+ reader->Read2s(&layer) &&
+ reader->Read2s(&alternate_group) &&
+ reader->Read2s(&volume) &&
+ reader->SkipBytes(2) && // reserved
+ reader->SkipBytes(36) && // matrix
+ reader->Read4(&width) &&
+ reader->Read4(&height));
+ width >>= 16;
+ height >>= 16;
+ return true;
+}
+
+SampleDescription::SampleDescription() {}
+SampleDescription::~SampleDescription() {}
+FourCC SampleDescription::BoxType() const { return FOURCC_STSD; }
+
+bool SampleDescription::Parse(BoxReader* reader) {
+ uint32 count;
+ RCHECK(reader->SkipBytes(4) &&
+ reader->Read4(&count) &&
+ reader->ScanChildren());
+ video_entries.clear();
+ audio_entries.clear();
+
+ // Note: this value is preset before scanning begins. See comments in the
+ // Parse(Media*) function.
+ if (type == kVideo) {
+ RCHECK(reader->ReadAllChildren(&video_entries));
+ } else if (type == kAudio) {
+ RCHECK(reader->ReadAllChildren(&audio_entries));
+ }
+ return true;
+}
+
+SampleTable::SampleTable() {}
+SampleTable::~SampleTable() {}
+FourCC SampleTable::BoxType() const { return FOURCC_STBL; }
+
+bool SampleTable::Parse(BoxReader* reader) {
+ return reader->ScanChildren() &&
+ reader->ReadChild(&description);
+}
+
+EditList::EditList() {}
+EditList::~EditList() {}
+FourCC EditList::BoxType() const { return FOURCC_ELST; }
+
+bool EditList::Parse(BoxReader* reader) {
+ uint32 count;
+ RCHECK(reader->ReadFullBoxHeader() && reader->Read4(&count));
+
+ if (reader->version() == 1) {
+ RCHECK(reader->HasBytes(count * 20));
+ } else {
+ RCHECK(reader->HasBytes(count * 12));
+ }
+ edits.resize(count);
+
+ for (std::vector<EditListEntry>::iterator edit = edits.begin();
+ edit != edits.end(); ++edit) {
+ if (reader->version() == 1) {
+ RCHECK(reader->Read8(&edit->segment_duration) &&
+ reader->Read8s(&edit->media_time));
+ } else {
+ RCHECK(reader->Read4Into8(&edit->segment_duration) &&
+ reader->Read4sInto8s(&edit->media_time));
+ }
+ RCHECK(reader->Read2s(&edit->media_rate_integer) &&
+ reader->Read2s(&edit->media_rate_fraction));
+ }
+ return true;
+}
+
+Edit::Edit() {}
+Edit::~Edit() {}
+FourCC Edit::BoxType() const { return FOURCC_EDTS; }
+
+bool Edit::Parse(BoxReader* reader) {
+ return reader->ScanChildren() && reader->ReadChild(&list);
+}
+
+HandlerReference::HandlerReference() {}
+HandlerReference::~HandlerReference() {}
+FourCC HandlerReference::BoxType() const { return FOURCC_HDLR; }
+
+bool HandlerReference::Parse(BoxReader* reader) {
+ FourCC hdlr_type;
+ RCHECK(reader->SkipBytes(8) && reader->ReadFourCC(&hdlr_type));
+ // Note: remaining fields in box ignored
+ if (hdlr_type == FOURCC_VIDE) {
+ type = kVideo;
+ } else if (hdlr_type == FOURCC_SOUN) {
+ type = kAudio;
+ } else {
+ type = kInvalid;
+ }
+ return true;
+}
+
+AVCDecoderConfigurationRecord::AVCDecoderConfigurationRecord() {}
+AVCDecoderConfigurationRecord::~AVCDecoderConfigurationRecord() {}
+FourCC AVCDecoderConfigurationRecord::BoxType() const { return FOURCC_AVCC; }
+
+bool AVCDecoderConfigurationRecord::Parse(BoxReader* reader) {
+ RCHECK(reader->Read1(&version) && version == 1 &&
+ reader->Read1(&profile_indication) &&
+ reader->Read1(&profile_compatibility) &&
+ reader->Read1(&avc_level));
+
+ uint8 length_size_minus_one;
+ RCHECK(reader->Read1(&length_size_minus_one) &&
+ (length_size_minus_one & 0xfc) == 0xfc);
+ length_size = (length_size_minus_one & 0x3) + 1;
+
+ uint8 num_sps;
+ RCHECK(reader->Read1(&num_sps) && (num_sps & 0xe0) == 0xe0);
+ num_sps &= 0x1f;
+
+ sps_list.resize(num_sps);
+ for (int i = 0; i < num_sps; i++) {
+ uint16 sps_length;
+ RCHECK(reader->Read2(&sps_length) &&
+ reader->ReadVec(&sps_list[i], sps_length));
+ }
+
+ uint8 num_pps;
+ RCHECK(reader->Read1(&num_pps));
+
+ pps_list.resize(num_pps);
+ for (int i = 0; i < num_pps; i++) {
+ uint16 pps_length;
+ RCHECK(reader->Read2(&pps_length) &&
+ reader->ReadVec(&pps_list[i], pps_length));
+ }
+
+ return true;
+}
+
+VideoSampleEntry::VideoSampleEntry() {}
+VideoSampleEntry::~VideoSampleEntry() {}
+FourCC VideoSampleEntry::BoxType() const {
+ DCHECK(false) << "VideoSampleEntry should be parsed according to the "
+ << "handler type recovered in its Media ancestor.";
+ return FOURCC_NULL;
+}
+
+bool VideoSampleEntry::Parse(BoxReader* reader) {
+ format = reader->type();
+ RCHECK(reader->SkipBytes(6) &&
+ reader->Read2(&data_reference_index) &&
+ reader->SkipBytes(16) &&
+ reader->Read2(&width) &&
+ reader->Read2(&height) &&
+ reader->SkipBytes(50));
+
+ RCHECK(reader->ScanChildren());
+ if (format == FOURCC_ENCV) {
+ RCHECK(reader->ReadChild(&sinf));
+ }
+
+ // TODO(strobe): finalize format signaling for encrypted media
+ // (http://crbug.com/132351)
+ //
+ // if (format == FOURCC_AVC1 ||
+ // (format == FOURCC_ENCV &&
+ // sinf.format.format == FOURCC_AVC1)) {
+ RCHECK(reader->ReadChild(&avcc));
+ // }
+ return true;
+}
+
+AudioSampleEntry::AudioSampleEntry() {}
+AudioSampleEntry::~AudioSampleEntry() {}
+FourCC AudioSampleEntry::BoxType() const {
+ DCHECK(false) << "AudioSampleEntry should be parsed according to the "
+ << "handler type recovered in its Media ancestor.";
+ return FOURCC_NULL;
+}
+
+bool AudioSampleEntry::Parse(BoxReader* reader) {
+ format = reader->type();
+ RCHECK(reader->SkipBytes(6) &&
+ reader->Read2(&data_reference_index) &&
+ reader->SkipBytes(8) &&
+ reader->Read2(&channelcount) &&
+ reader->Read2(&samplesize) &&
+ reader->SkipBytes(4) &&
+ reader->Read4(&samplerate));
+ // Convert from 16.16 fixed point to integer
+ samplerate >>= 16;
+
+ RCHECK(reader->ScanChildren());
+ if (format == FOURCC_ENCA) {
+ RCHECK(reader->ReadChild(&sinf));
+ }
+ return true;
+}
+
+MediaHeader::MediaHeader() {}
+MediaHeader::~MediaHeader() {}
+FourCC MediaHeader::BoxType() const { return FOURCC_MDHD; }
+
+bool MediaHeader::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader());
+
+ if (reader->version() == 1) {
+ RCHECK(reader->Read8(&creation_time) &&
+ reader->Read8(&modification_time) &&
+ reader->Read4(&timescale) &&
+ reader->Read8(&duration));
+ } else {
+ RCHECK(reader->Read4Into8(&creation_time) &&
+ reader->Read4Into8(&modification_time) &&
+ reader->Read4(&timescale) &&
+ reader->Read4Into8(&duration));
+ }
+ // Skip language information
+ return reader->SkipBytes(4);
+}
+
+MediaInformation::MediaInformation() {}
+MediaInformation::~MediaInformation() {}
+FourCC MediaInformation::BoxType() const { return FOURCC_MINF; }
+
+bool MediaInformation::Parse(BoxReader* reader) {
+ return reader->ScanChildren() &&
+ reader->ReadChild(&sample_table);
+}
+
+Media::Media() {}
+Media::~Media() {}
+FourCC Media::BoxType() const { return FOURCC_MDIA; }
+
+bool Media::Parse(BoxReader* reader) {
+ RCHECK(reader->ScanChildren() &&
+ reader->ReadChild(&header) &&
+ reader->ReadChild(&handler));
+
+ // Maddeningly, the HandlerReference box specifies how to parse the
+ // SampleDescription box, making the latter the only box (of those that we
+ // support) which cannot be parsed correctly on its own (or even with
+ // information from its strict ancestor tree). We thus copy the handler type
+ // to the sample description box *before* parsing it to provide this
+ // information while parsing.
+ information.sample_table.description.type = handler.type;
+ RCHECK(reader->ReadChild(&information));
+ return true;
+}
+
+Track::Track() {}
+Track::~Track() {}
+FourCC Track::BoxType() const { return FOURCC_TRAK; }
+
+bool Track::Parse(BoxReader* reader) {
+ RCHECK(reader->ScanChildren() &&
+ reader->ReadChild(&header) &&
+ reader->ReadChild(&media) &&
+ reader->MaybeReadChild(&edit));
+ return true;
+}
+
+MovieExtendsHeader::MovieExtendsHeader() {}
+MovieExtendsHeader::~MovieExtendsHeader() {}
+FourCC MovieExtendsHeader::BoxType() const { return FOURCC_MEHD; }
+
+bool MovieExtendsHeader::Parse(BoxReader* reader) {
+ RCHECK(reader->Read8(&fragment_duration));
+ return true;
+}
+
+TrackExtends::TrackExtends() {}
+TrackExtends::~TrackExtends() {}
+FourCC TrackExtends::BoxType() const { return FOURCC_TREX; }
+
+bool TrackExtends::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader() &&
+ reader->Read4(&track_id) &&
+ reader->Read4(&default_sample_description_index) &&
+ reader->Read4(&default_sample_duration) &&
+ reader->Read4(&default_sample_size) &&
+ reader->Read4(&default_sample_flags));
+ return true;
+}
+
+MovieExtends::MovieExtends() {}
+MovieExtends::~MovieExtends() {}
+FourCC MovieExtends::BoxType() const { return FOURCC_MVEX; }
+
+bool MovieExtends::Parse(BoxReader* reader) {
+ header.fragment_duration = 0;
+ return reader->ScanChildren() &&
+ reader->MaybeReadChild(&header) &&
+ reader->ReadChildren(&tracks);
+}
+
+Movie::Movie() {}
+Movie::~Movie() {}
+FourCC Movie::BoxType() const { return FOURCC_MOOV; }
+
+bool Movie::Parse(BoxReader* reader) {
+ return reader->ScanChildren() &&
+ reader->ReadChild(&header) &&
+ reader->ReadChildren(&tracks) &&
+ // Media Source specific: 'mvex' required
+ reader->ReadChild(&extends) &&
+ reader->MaybeReadChildren(&pssh);
+}
+
+TrackFragmentDecodeTime::TrackFragmentDecodeTime() {}
+TrackFragmentDecodeTime::~TrackFragmentDecodeTime() {}
+FourCC TrackFragmentDecodeTime::BoxType() const { return FOURCC_TFDT; }
+
+bool TrackFragmentDecodeTime::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader());
+ if (reader->version() == 1)
+ return reader->Read8(&decode_time);
+ else
+ return reader->Read4Into8(&decode_time);
+}
+
+MovieFragmentHeader::MovieFragmentHeader() {}
+MovieFragmentHeader::~MovieFragmentHeader() {}
+FourCC MovieFragmentHeader::BoxType() const { return FOURCC_MFHD; }
+
+bool MovieFragmentHeader::Parse(BoxReader* reader) {
+ return reader->SkipBytes(4) && reader->Read4(&sequence_number);
+}
+
+TrackFragmentHeader::TrackFragmentHeader() {}
+TrackFragmentHeader::~TrackFragmentHeader() {}
+FourCC TrackFragmentHeader::BoxType() const { return FOURCC_TFHD; }
+
+bool TrackFragmentHeader::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader() && reader->Read4(&track_id));
+
+ // Media Source specific: reject tracks that set 'base-data-offset-present'.
+ // Although the Media Source requires that 'default-base-is-moof' (14496-12
+ // Amendment 2) be set, we omit this check as many otherwise-valid files in
+ // the wild don't set it.
+ //
+ // RCHECK((flags & 0x020000) && !(flags & 0x1));
+ RCHECK(!(reader->flags() & 0x1));
+
+ if (reader->flags() & 0x2)
+ RCHECK(reader->SkipBytes(4)); // sample_description_index
+
+ if (reader->flags() & 0x8) {
+ RCHECK(reader->Read4(&default_sample_duration));
+ } else {
+ default_sample_duration = 0;
+ }
+
+ if (reader->flags() & 0x10) {
+ RCHECK(reader->Read4(&default_sample_size));
+ } else {
+ default_sample_size = 0;
+ }
+
+ if (reader->flags() & 0x20) {
+ RCHECK(reader->Read4(&default_sample_flags));
+ has_default_sample_flags = true;
+ } else {
+ has_default_sample_flags = false;
+ }
+
+ return true;
+}
+
+TrackFragmentRun::TrackFragmentRun() {}
+TrackFragmentRun::~TrackFragmentRun() {}
+FourCC TrackFragmentRun::BoxType() const { return FOURCC_TRUN; }
+
+bool TrackFragmentRun::Parse(BoxReader* reader) {
+ RCHECK(reader->ReadFullBoxHeader() &&
+ reader->Read4(&sample_count));
+ const uint32 flags = reader->flags();
+
+ bool data_offset_present = (flags & 0x1) != 0;
+ bool first_sample_flags_present = (flags & 0x4) != 0;
+ bool sample_duration_present = (flags & 0x100) != 0;
+ bool sample_size_present = (flags & 0x200) != 0;
+ bool sample_flags_present = (flags & 0x400) != 0;
+ bool sample_composition_time_offsets_present = (flags & 0x800) != 0;
+
+ if (data_offset_present) {
+ RCHECK(reader->Read4(&data_offset));
+ } else {
+ data_offset = 0;
+ }
+
+ uint32 first_sample_flags;
+ if (first_sample_flags_present)
+ RCHECK(reader->Read4(&first_sample_flags));
+
+ int fields = sample_duration_present + sample_size_present +
+ sample_flags_present + sample_composition_time_offsets_present;
+ RCHECK(reader->HasBytes(fields * sample_count));
+
+ if (sample_duration_present)
+ sample_durations.resize(sample_count);
+ if (sample_size_present)
+ sample_sizes.resize(sample_count);
+ if (sample_flags_present)
+ sample_flags.resize(sample_count);
+ if (sample_composition_time_offsets_present)
+ sample_composition_time_offsets.resize(sample_count);
+
+ for (uint32 i = 0; i < sample_count; ++i) {
+ if (sample_duration_present)
+ RCHECK(reader->Read4(&sample_durations[i]));
+ if (sample_size_present)
+ RCHECK(reader->Read4(&sample_sizes[i]));
+ if (sample_flags_present)
+ RCHECK(reader->Read4(&sample_flags[i]));
+ if (sample_composition_time_offsets_present)
+ RCHECK(reader->Read4(&sample_composition_time_offsets[i]));
+ }
+
+ if (first_sample_flags_present) {
+ if (sample_flags.size() == 0) {
+ sample_flags.push_back(first_sample_flags);
+ } else {
+ sample_flags[0] = first_sample_flags;
+ }
+ }
+ return true;
+}
+
+TrackFragment::TrackFragment() {}
+TrackFragment::~TrackFragment() {}
+FourCC TrackFragment::BoxType() const { return FOURCC_TRAF; }
+
+bool TrackFragment::Parse(BoxReader* reader) {
+ return reader->ScanChildren() &&
+ reader->ReadChild(&header) &&
+ // Media Source specific: 'tfdt' required
+ reader->ReadChild(&decode_time) &&
+ reader->MaybeReadChildren(&runs) &&
+ reader->MaybeReadChild(&auxiliary_offset) &&
+ reader->MaybeReadChild(&auxiliary_size);
+}
+
+MovieFragment::MovieFragment() {}
+MovieFragment::~MovieFragment() {}
+FourCC MovieFragment::BoxType() const { return FOURCC_MOOF; }
+
+bool MovieFragment::Parse(BoxReader* reader) {
+ RCHECK(reader->ScanChildren() &&
+ reader->ReadChild(&header) &&
+ reader->ReadChildren(&tracks) &&
+ reader->MaybeReadChildren(&pssh));
+ return true;
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/box_definitions.h b/media/mp4/box_definitions.h
new file mode 100644
index 0000000..594e69b
--- /dev/null
+++ b/media/mp4/box_definitions.h
@@ -0,0 +1,330 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_BOX_DEFINITIONS_H_
+#define MEDIA_MP4_BOX_DEFINITIONS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "media/base/media_export.h"
+#include "media/mp4/avc.h"
+#include "media/mp4/box_reader.h"
+#include "media/mp4/fourccs.h"
+
+namespace media {
+namespace mp4 {
+
+enum TrackType {
+ kInvalid = 0,
+ kVideo,
+ kAudio,
+ kHint
+};
+
+#define DECLARE_BOX_METHODS(T) \
+ T(); \
+ virtual ~T(); \
+ virtual bool Parse(BoxReader* reader) OVERRIDE; \
+ virtual FourCC BoxType() const OVERRIDE; \
+
+struct FileType : Box {
+ DECLARE_BOX_METHODS(FileType);
+
+ FourCC major_brand;
+ uint32 minor_version;
+};
+
+struct ProtectionSystemSpecificHeader : Box {
+ DECLARE_BOX_METHODS(ProtectionSystemSpecificHeader);
+
+ std::vector<uint8> system_id;
+ std::vector<uint8> data;
+};
+
+struct SampleAuxiliaryInformationOffset : Box {
+ DECLARE_BOX_METHODS(SampleAuxiliaryInformationOffset);
+
+ std::vector<uint64> offsets;
+};
+
+struct SampleAuxiliaryInformationSize : Box {
+ DECLARE_BOX_METHODS(SampleAuxiliaryInformationSize);
+
+ uint8 default_sample_info_size;
+ uint32 sample_count;
+ std::vector<uint8> sample_info_sizes;
+};
+
+struct OriginalFormat : Box {
+ DECLARE_BOX_METHODS(OriginalFormat);
+
+ FourCC format;
+};
+
+struct SchemeType : Box {
+ DECLARE_BOX_METHODS(SchemeType);
+
+ FourCC type;
+ uint32 version;
+};
+
+struct TrackEncryption : Box {
+ DECLARE_BOX_METHODS(TrackEncryption);
+
+ // Note: this definition is specific to the CENC protection type.
+ bool is_encrypted;
+ uint8 default_iv_size;
+ std::vector<uint8> default_kid;
+};
+
+struct SchemeInfo : Box {
+ DECLARE_BOX_METHODS(SchemeInfo);
+
+ TrackEncryption track_encryption;
+};
+
+struct ProtectionSchemeInfo : Box {
+ DECLARE_BOX_METHODS(ProtectionSchemeInfo);
+
+ OriginalFormat format;
+ SchemeType type;
+ SchemeInfo info;
+};
+
+struct MovieHeader : Box {
+ DECLARE_BOX_METHODS(MovieHeader);
+
+ uint64 creation_time;
+ uint64 modification_time;
+ uint32 timescale;
+ uint64 duration;
+ int32 rate;
+ int16 volume;
+ uint32 next_track_id;
+};
+
+struct TrackHeader : Box {
+ DECLARE_BOX_METHODS(TrackHeader);
+
+ uint64 creation_time;
+ uint64 modification_time;
+ uint32 track_id;
+ uint64 duration;
+ int16 layer;
+ int16 alternate_group;
+ int16 volume;
+ uint32 width;
+ uint32 height;
+};
+
+struct EditListEntry {
+ uint64 segment_duration;
+ int64 media_time;
+ int16 media_rate_integer;
+ int16 media_rate_fraction;
+};
+
+struct EditList : Box {
+ DECLARE_BOX_METHODS(EditList);
+
+ std::vector<EditListEntry> edits;
+};
+
+struct Edit : Box {
+ DECLARE_BOX_METHODS(Edit);
+
+ EditList list;
+};
+
+struct HandlerReference : Box {
+ DECLARE_BOX_METHODS(HandlerReference);
+
+ TrackType type;
+};
+
+struct MEDIA_EXPORT AVCDecoderConfigurationRecord : Box {
+ DECLARE_BOX_METHODS(AVCDecoderConfigurationRecord);
+
+ uint8 version;
+ uint8 profile_indication;
+ uint8 profile_compatibility;
+ uint8 avc_level;
+ uint8 length_size;
+
+ typedef std::vector<uint8> SPS;
+ typedef std::vector<uint8> PPS;
+
+ std::vector<SPS> sps_list;
+ std::vector<PPS> pps_list;
+};
+
+struct VideoSampleEntry : Box {
+ DECLARE_BOX_METHODS(VideoSampleEntry);
+
+ FourCC format;
+ uint16 data_reference_index;
+ uint16 width;
+ uint16 height;
+
+ ProtectionSchemeInfo sinf;
+
+ // Currently expected to be present regardless of format.
+ AVCDecoderConfigurationRecord avcc;
+};
+
+struct AudioSampleEntry : Box {
+ DECLARE_BOX_METHODS(AudioSampleEntry);
+
+ FourCC format;
+ uint16 data_reference_index;
+ uint16 channelcount;
+ uint16 samplesize;
+ uint32 samplerate;
+
+ ProtectionSchemeInfo sinf;
+};
+
+struct SampleDescription : Box {
+ DECLARE_BOX_METHODS(SampleDescription);
+
+ TrackType type;
+ std::vector<VideoSampleEntry> video_entries;
+ std::vector<AudioSampleEntry> audio_entries;
+};
+
+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.
+ SampleDescription description;
+};
+
+struct MediaHeader : Box {
+ DECLARE_BOX_METHODS(MediaHeader);
+
+ uint64 creation_time;
+ uint64 modification_time;
+ uint32 timescale;
+ uint64 duration;
+};
+
+struct MediaInformation : Box {
+ DECLARE_BOX_METHODS(MediaInformation);
+
+ SampleTable sample_table;
+};
+
+struct Media : Box {
+ DECLARE_BOX_METHODS(Media);
+
+ MediaHeader header;
+ HandlerReference handler;
+ MediaInformation information;
+};
+
+struct Track : Box {
+ DECLARE_BOX_METHODS(Track);
+
+ TrackHeader header;
+ Media media;
+ Edit edit;
+};
+
+struct MovieExtendsHeader : Box {
+ DECLARE_BOX_METHODS(MovieExtendsHeader);
+
+ uint64 fragment_duration;
+};
+
+struct TrackExtends : Box {
+ DECLARE_BOX_METHODS(TrackExtends);
+
+ uint32 track_id;
+ uint32 default_sample_description_index;
+ uint32 default_sample_duration;
+ uint32 default_sample_size;
+ uint32 default_sample_flags;
+};
+
+struct MovieExtends : Box {
+ DECLARE_BOX_METHODS(MovieExtends);
+
+ MovieExtendsHeader header;
+ std::vector<TrackExtends> tracks;
+};
+
+struct Movie : Box {
+ DECLARE_BOX_METHODS(Movie);
+
+ bool fragmented;
+ MovieHeader header;
+ MovieExtends extends;
+ std::vector<Track> tracks;
+ std::vector<ProtectionSystemSpecificHeader> pssh;
+};
+
+struct TrackFragmentDecodeTime : Box {
+ DECLARE_BOX_METHODS(TrackFragmentDecodeTime);
+
+ uint64 decode_time;
+};
+
+struct MovieFragmentHeader : Box {
+ DECLARE_BOX_METHODS(MovieFragmentHeader);
+
+ uint32 sequence_number;
+};
+
+struct TrackFragmentHeader : Box {
+ DECLARE_BOX_METHODS(TrackFragmentHeader);
+
+ uint32 track_id;
+ uint32 default_sample_duration;
+ uint32 default_sample_size;
+ uint32 default_sample_flags;
+
+ // As 'flags' might be all zero, we cannot use zeroness alone to identify
+ // when default_sample_flags wasn't specified, unlike the other values.
+ bool has_default_sample_flags;
+};
+
+struct TrackFragmentRun : Box {
+ DECLARE_BOX_METHODS(TrackFragmentRun);
+
+ uint32 sample_count;
+ uint32 data_offset;
+ std::vector<uint32> sample_flags;
+ std::vector<uint32> sample_sizes;
+ std::vector<uint32> sample_durations;
+ std::vector<uint32> sample_composition_time_offsets;
+};
+
+struct TrackFragment : Box {
+ DECLARE_BOX_METHODS(TrackFragment);
+
+ TrackFragmentHeader header;
+ std::vector<TrackFragmentRun> runs;
+ TrackFragmentDecodeTime decode_time;
+ SampleAuxiliaryInformationOffset auxiliary_offset;
+ SampleAuxiliaryInformationSize auxiliary_size;
+};
+
+struct MovieFragment : Box {
+ DECLARE_BOX_METHODS(MovieFragment);
+
+ MovieFragmentHeader header;
+ std::vector<TrackFragment> tracks;
+ std::vector<ProtectionSystemSpecificHeader> pssh;
+};
+
+#undef DECLARE_BOX
+
+} // namespace mp4
+} // namespace media
+
+#endif // MEDIA_MP4_BOX_DEFINITIONS_H_
diff --git a/media/mp4/box_reader.cc b/media/mp4/box_reader.cc
new file mode 100644
index 0000000..05690a7
--- /dev/null
+++ b/media/mp4/box_reader.cc
@@ -0,0 +1,193 @@
+// Copyright (c) 2012 The Chromium Authors. 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/box_reader.h"
+
+#include <string.h>
+#include <algorithm>
+#include <map>
+#include <set>
+
+#include "base/logging.h"
+#include "media/mp4/box_definitions.h"
+#include "media/mp4/rcheck.h"
+
+namespace media {
+namespace mp4 {
+
+Box::~Box() {}
+
+bool BufferReader::Read1(uint8* v) {
+ RCHECK(HasBytes(1));
+ *v = buf_[pos_++];
+ return true;
+}
+
+// Internal implementation of multi-byte reads
+template<typename T> bool BufferReader::Read(T* v) {
+ RCHECK(HasBytes(sizeof(T)));
+
+ T tmp = 0;
+ for (size_t i = 0; i < sizeof(T); i++) {
+ tmp <<= 8;
+ tmp += buf_[pos_++];
+ }
+ *v = tmp;
+ return true;
+}
+
+bool BufferReader::Read2(uint16* v) { return Read(v); }
+bool BufferReader::Read2s(int16* v) { return Read(v); }
+bool BufferReader::Read4(uint32* v) { return Read(v); }
+bool BufferReader::Read4s(int32* v) { return Read(v); }
+bool BufferReader::Read8(uint64* v) { return Read(v); }
+bool BufferReader::Read8s(int64* v) { return Read(v); }
+
+bool BufferReader::ReadFourCC(FourCC* v) {
+ return Read4(reinterpret_cast<uint32*>(v));
+}
+
+bool BufferReader::ReadVec(std::vector<uint8>* vec, int count) {
+ RCHECK(HasBytes(count));
+ vec->clear();
+ vec->insert(vec->end(), buf_ + pos_, buf_ + pos_ + count);
+ pos_ += count;
+ return true;
+}
+
+bool BufferReader::SkipBytes(int bytes) {
+ RCHECK(HasBytes(bytes));
+ pos_ += bytes;
+ return true;
+}
+
+bool BufferReader::Read4Into8(uint64* v) {
+ uint32 tmp;
+ RCHECK(Read4(&tmp));
+ *v = tmp;
+ return true;
+}
+
+bool BufferReader::Read4sInto8s(int64* v) {
+ // Beware of the need for sign extension.
+ int32 tmp;
+ RCHECK(Read4s(&tmp));
+ *v = tmp;
+ return true;
+}
+
+
+BoxReader::BoxReader(const uint8* buf, const int size)
+ : BufferReader(buf, size), scanned_(false) {
+}
+
+BoxReader::~BoxReader() {
+ if (scanned_ && !children_.empty()) {
+ for (ChildMap::iterator itr = children_.begin();
+ itr != children_.end(); ++itr) {
+ DVLOG(1) << "Skipping unknown box: " << FourCCToString(itr->first);
+ }
+ }
+}
+
+BoxReader* BoxReader::ReadTopLevelBox(const uint8* buf,
+ const int buf_size,
+ bool* err) {
+ BoxReader* reader = new BoxReader(buf, buf_size);
+ if (reader->ReadHeader(err) && reader->size() <= buf_size) {
+ return reader;
+ }
+ delete reader;
+ return NULL;
+}
+
+// static
+bool BoxReader::StartTopLevelBox(const uint8* buf,
+ const int buf_size,
+ FourCC* type,
+ int* box_size,
+ bool* err) {
+ BoxReader reader(buf, buf_size);
+ if (!reader.ReadHeader(err)) return false;
+ *type = reader.type();
+ *box_size = reader.size();
+ return true;
+}
+
+bool BoxReader::ScanChildren() {
+ DCHECK(!scanned_);
+ scanned_ = true;
+
+ bool err;
+ // TODO(strobe): Check or correct for multimap not inserting elements in
+ // consistent order.
+ while (pos() < size()) {
+ BoxReader child(&buf_[pos_], size_ - pos_);
+ if (!child.ReadHeader(&err)) break;
+
+ children_.insert(std::pair<FourCC, BoxReader>(child.type(), child));
+ pos_ += child.size();
+ }
+
+ DCHECK(!err);
+ return !err && pos() == size();
+}
+
+bool BoxReader::ReadChild(Box* child) {
+ DCHECK(scanned_);
+ FourCC child_type = child->BoxType();
+
+ ChildMap::iterator itr = children_.find(child_type);
+ RCHECK(itr != children_.end());
+ DVLOG(2) << "Found a " << FourCCToString(child_type) << " box.";
+ RCHECK(child->Parse(&itr->second));
+ children_.erase(itr);
+ return true;
+}
+
+bool BoxReader::MaybeReadChild(Box* child) {
+ if (!children_.count(child->BoxType())) return true;
+ return ReadChild(child);
+}
+
+bool BoxReader::ReadFullBoxHeader() {
+ uint32 vflags;
+ RCHECK(Read4(&vflags));
+ version_ = vflags >> 24;
+ flags_ = vflags & 0xffffff;
+ return true;
+}
+
+bool BoxReader::ReadHeader(bool* err) {
+ uint64 size = 0;
+ *err = false;
+
+ if (!HasBytes(8)) return false;
+ CHECK(Read4Into8(&size) && ReadFourCC(&type_));
+
+ if (size == 0) {
+ // Media Source specific: we do not support boxes that run to EOS.
+ *err = true;
+ return false;
+ } else if (size == 1) {
+ if (!HasBytes(8)) return false;
+ CHECK(Read8(&size));
+ }
+
+ // Implementation-specific: support for boxes larger than 2^31 has been
+ // removed.
+ if (size < static_cast<uint64>(pos_) ||
+ size > static_cast<uint64>(kint32max)) {
+ *err = true;
+ return false;
+ }
+
+ // Note that the pos_ head has advanced to the byte immediately after the
+ // header, which is where we want it.
+ size_ = size;
+ return true;
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/box_reader.h b/media/mp4/box_reader.h
new file mode 100644
index 0000000..1898c73
--- /dev/null
+++ b/media/mp4/box_reader.h
@@ -0,0 +1,201 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_BOX_READER_H_
+#define MEDIA_MP4_BOX_READER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "media/base/media_export.h"
+#include "media/mp4/fourccs.h"
+#include "media/mp4/rcheck.h"
+
+namespace media {
+namespace mp4 {
+
+class BoxReader;
+
+struct MEDIA_EXPORT Box {
+ virtual ~Box();
+ virtual bool Parse(BoxReader* reader) = 0;
+ virtual FourCC BoxType() const = 0;
+};
+
+class MEDIA_EXPORT BufferReader {
+ public:
+ BufferReader(const uint8* buf, const int size)
+ : buf_(buf), size_(size), pos_(0) {}
+
+ bool HasBytes(int count) { return (pos() + count <= size()); }
+
+ // Read a value from the stream, perfoming endian correction, and advance the
+ // stream pointer.
+ bool Read1(uint8* v) WARN_UNUSED_RESULT;
+ bool Read2(uint16* v) WARN_UNUSED_RESULT;
+ bool Read2s(int16* v) WARN_UNUSED_RESULT;
+ bool Read4(uint32* v) WARN_UNUSED_RESULT;
+ bool Read4s(int32* v) WARN_UNUSED_RESULT;
+ bool Read8(uint64* v) WARN_UNUSED_RESULT;
+ bool Read8s(int64* v) WARN_UNUSED_RESULT;
+
+ bool ReadFourCC(FourCC* v) WARN_UNUSED_RESULT;
+
+ bool ReadVec(std::vector<uint8>* t, int count) WARN_UNUSED_RESULT;
+
+ // These variants read a 4-byte integer of the corresponding signedness and
+ // store it in the 8-byte return type.
+ bool Read4Into8(uint64* v) WARN_UNUSED_RESULT;
+ bool Read4sInto8s(int64* v) WARN_UNUSED_RESULT;
+
+ // Advance the stream by this many bytes.
+ bool SkipBytes(int nbytes) WARN_UNUSED_RESULT;
+
+ int size() { return size_; }
+ int pos() { return pos_; }
+
+ protected:
+ const uint8* buf_;
+ int size_;
+ int pos_;
+
+ template<typename T> bool Read(T* t) WARN_UNUSED_RESULT;
+};
+
+class MEDIA_EXPORT BoxReader : public BufferReader {
+ public:
+ ~BoxReader();
+
+ // Create a BoxReader from a buffer. Note that this function may return NULL
+ // if an intact, complete box was not available in the buffer. If |*err| is
+ // set, there was a stream-level error when creating the box; otherwise, NULL
+ // values are only expected when insufficient data is available.
+ //
+ // |buf| is retained but not owned, and must outlive the BoxReader instance.
+ static BoxReader* ReadTopLevelBox(const uint8* buf,
+ const int buf_size,
+ bool* err);
+
+ // Read the box header from the current buffer. This function returns true if
+ // there is enough data to read the header and the header is sane; that is, it
+ // does not check to ensure the entire box is in the buffer before returning
+ // true. The semantics of |*err| are the same as above.
+ //
+ // |buf| is not retained.
+ static bool StartTopLevelBox(const uint8* buf,
+ const int buf_size,
+ FourCC* type,
+ int* box_size,
+ bool* err) WARN_UNUSED_RESULT;
+
+ // Scan through all boxes within the current box, starting at the current
+ // buffer position. Must be called before any of the *Child functions work.
+ bool ScanChildren() 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;
+
+ // Read one child if available. Returns false on error, true on successful
+ // read or on child absent.
+ bool MaybeReadChild(Box* child) WARN_UNUSED_RESULT;
+
+ // Read at least one child. False means error or no such child present.
+ template<typename T> bool ReadChildren(
+ std::vector<T>* children) WARN_UNUSED_RESULT;
+
+ // Read any number of children. False means error.
+ template<typename T> bool MaybeReadChildren(
+ std::vector<T>* children) WARN_UNUSED_RESULT;
+
+ // Read all children, regardless of FourCC. This is used from exactly one box,
+ // corresponding to a rather significant inconsistency in the BMFF spec.
+ template<typename T> bool ReadAllChildren(
+ std::vector<T>* children) WARN_UNUSED_RESULT;
+
+ // Populate the values of 'version()' and 'flags()' from a full box header.
+ // Many boxes, but not all, use these values. This call should happen after
+ // the box has been initialized, and does not re-read the main box header.
+ bool ReadFullBoxHeader() WARN_UNUSED_RESULT;
+
+ FourCC type() const { return type_; }
+ uint8 version() const { return version_; }
+ uint32 flags() const { return flags_; }
+
+ private:
+ BoxReader(const uint8* buf, const int size);
+
+ // Must be called immediately after init. If the return is false, this
+ // indicates that the box header and its contents were not available in the
+ // stream or were nonsensical, and that the box must not be used further. In
+ // this case, if |*err| is false, the problem was simply a lack of data, and
+ // should only be an error condition if some higher-level component knows that
+ // no more data is coming (i.e. EOS or end of containing box). If |*err| is
+ // true, the error is unrecoverable and the stream should be aborted.
+ bool ReadHeader(bool* err);
+
+ FourCC type_;
+ uint8 version_;
+ uint32 flags_;
+
+ typedef std::multimap<FourCC, BoxReader> ChildMap;
+
+ // The set of child box FourCCs and their corresponding buffer readers. Only
+ // valid if scanned_ is true.
+ ChildMap children_;
+ bool scanned_;
+};
+
+// Template definitions
+template<typename T> bool BoxReader::ReadChildren(std::vector<T>* children) {
+ RCHECK(MaybeReadChildren(children) && !children->empty());
+ return true;
+}
+
+template<typename T>
+bool BoxReader::MaybeReadChildren(std::vector<T>* children) {
+ DCHECK(scanned_);
+ DCHECK(children->empty());
+
+ children->resize(1);
+ FourCC child_type = (*children)[0].BoxType();
+
+ ChildMap::iterator start_itr = children_.lower_bound(child_type);
+ ChildMap::iterator end_itr = children_.upper_bound(child_type);
+ children->resize(std::distance(start_itr, end_itr));
+ typename std::vector<T>::iterator child_itr = children->begin();
+ for (ChildMap::iterator itr = start_itr; itr != end_itr; ++itr) {
+ RCHECK(child_itr->Parse(&itr->second));
+ ++child_itr;
+ }
+ children_.erase(start_itr, end_itr);
+
+ DVLOG(2) << "Found " << children->size() << " "
+ << FourCCToString(child_type) << " boxes.";
+ return true;
+}
+
+template<typename T>
+bool BoxReader::ReadAllChildren(std::vector<T>* children) {
+ DCHECK(scanned_);
+ DCHECK(children->empty());
+ RCHECK(!children_.empty());
+
+ children->resize(children_.size());
+ typename std::vector<T>::iterator child_itr = children->begin();
+ for (ChildMap::iterator itr = children_.begin();
+ itr != children_.end(); ++itr) {
+ RCHECK(child_itr->Parse(&itr->second));
+ ++child_itr;
+ }
+ children_.clear();
+ return true;
+}
+
+} // namespace mp4
+} // namespace media
+
+#endif // MEDIA_MP4_BOX_READER_H_
diff --git a/media/mp4/box_reader_unittest.cc b/media/mp4/box_reader_unittest.cc
new file mode 100644
index 0000000..4c5fc71
--- /dev/null
+++ b/media/mp4/box_reader_unittest.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/mp4/box_reader.h"
+#include "media/mp4/rcheck.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+namespace mp4 {
+
+static const uint8 kTestBox[] = {
+ // Test box containing three children
+ 0x00, 0x00, 0x00, 0x40, 't', 'e', 's', 't',
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0xf9, 0x0a, 0x0b, 0x0c, 0xfd, 0x0e, 0x0f, 0x10,
+ // Ordinary child box
+ 0x00, 0x00, 0x00, 0x0c, 'c', 'h', 'l', 'd', 0xde, 0xad, 0xbe, 0xef,
+ // Extended-size child box
+ 0x00, 0x00, 0x00, 0x01, 'c', 'h', 'l', 'd',
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
+ 0xfa, 0xce, 0xca, 0xfe,
+ // Empty box
+ 0x00, 0x00, 0x00, 0x08, 'm', 'p', 't', 'y',
+ // Trailing garbage
+ 0x00 };
+
+struct EmptyBox : Box {
+ virtual bool Parse(BoxReader* reader) OVERRIDE {
+ return true;
+ }
+ virtual FourCC BoxType() const OVERRIDE { return FOURCC_MPTY; }
+};
+
+struct ChildBox : Box {
+ uint32 val;
+
+ virtual bool Parse(BoxReader* reader) OVERRIDE {
+ return reader->Read4(&val);
+ }
+ virtual FourCC BoxType() const OVERRIDE { return FOURCC_CHLD; }
+};
+
+struct TestBox : Box {
+ uint8 a, b;
+ uint16 c;
+ int32 d;
+ int64 e;
+
+ std::vector<ChildBox> kids;
+ EmptyBox mpty;
+
+ virtual bool Parse(BoxReader* reader) OVERRIDE {
+ RCHECK(reader->ReadFullBoxHeader() &&
+ reader->Read1(&a) &&
+ reader->Read1(&b) &&
+ reader->Read2(&c) &&
+ reader->Read4s(&d) &&
+ reader->Read4sInto8s(&e));
+ return reader->ScanChildren() &&
+ reader->ReadChildren(&kids) &&
+ reader->MaybeReadChild(&mpty);
+ }
+ virtual FourCC BoxType() const OVERRIDE { return FOURCC_TEST; }
+
+ TestBox();
+ ~TestBox();
+};
+
+TestBox::TestBox() {}
+TestBox::~TestBox() {}
+
+class BoxReaderTest : public testing::Test {
+ protected:
+ std::vector<uint8> GetBuf() {
+ return std::vector<uint8>(kTestBox, kTestBox + sizeof(kTestBox));
+ }
+};
+
+TEST_F(BoxReaderTest, ExpectedOperationTest) {
+ std::vector<uint8> buf = GetBuf();
+ bool err;
+ scoped_ptr<BoxReader> reader(
+ BoxReader::ReadTopLevelBox(&buf[0], buf.size(), &err));
+ EXPECT_FALSE(err);
+ EXPECT_TRUE(reader.get());
+
+ TestBox box;
+ EXPECT_TRUE(box.Parse(reader.get()));
+ EXPECT_EQ(0x01, reader->version());
+ EXPECT_EQ(0x020304u, reader->flags());
+ EXPECT_EQ(0x05, box.a);
+ EXPECT_EQ(0x06, box.b);
+ EXPECT_EQ(0x0708, box.c);
+ EXPECT_EQ(static_cast<int32>(0xf90a0b0c), box.d);
+ EXPECT_EQ(static_cast<int32>(0xfd0e0f10), box.e);
+
+ EXPECT_EQ(2u, box.kids.size());
+ EXPECT_EQ(0xdeadbeef, box.kids[0].val);
+ EXPECT_EQ(0xfacecafe, box.kids[1].val);
+
+ // Accounting for the extra byte outside of the box above
+ EXPECT_EQ(buf.size(), static_cast<uint64>(reader->size() + 1));
+}
+
+TEST_F(BoxReaderTest, OuterTooShortTest) {
+ std::vector<uint8> buf = GetBuf();
+ bool err;
+
+ // Create a soft failure by truncating the outer box.
+ scoped_ptr<BoxReader> r(
+ BoxReader::ReadTopLevelBox(&buf[0], buf.size() - 2, &err));
+
+ EXPECT_FALSE(err);
+ EXPECT_FALSE(r.get());
+}
+
+TEST_F(BoxReaderTest, InnerTooLongTest) {
+ std::vector<uint8> buf = GetBuf();
+ bool err;
+
+ // Make an inner box too big for its outer box.
+ buf[25] = 1;
+ scoped_ptr<BoxReader> reader(
+ BoxReader::ReadTopLevelBox(&buf[0], buf.size(), &err));
+
+ TestBox box;
+ EXPECT_FALSE(box.Parse(reader.get()));
+}
+
+TEST_F(BoxReaderTest, WrongFourCCTest) {
+ std::vector<uint8> buf = GetBuf();
+ bool err;
+
+ // Use an unknown FourCC both on an outer box and an inner one.
+ buf[5] = 1;
+ buf[28] = 1;
+ scoped_ptr<BoxReader> reader(
+ BoxReader::ReadTopLevelBox(&buf[0], buf.size(), &err));
+
+ TestBox box;
+ std::vector<ChildBox> kids;
+ // This should still work; the outer box reader doesn't care about the FourCC,
+ // since it assumes you've already examined it before deciding what to parse.
+ EXPECT_TRUE(box.Parse(reader.get()));
+ EXPECT_EQ(0x74017374, reader->type());
+ // Parsing the TestBox should have left the modified inner box unread, which
+ // we collect here.
+ EXPECT_TRUE(reader->ReadAllChildren(&kids));
+ EXPECT_EQ(1u, kids.size());
+ EXPECT_EQ(0xdeadbeef, kids[0].val);
+}
+
+TEST_F(BoxReaderTest, ChildrenTest) {
+ std::vector<uint8> buf = GetBuf();
+ bool err;
+ scoped_ptr<BoxReader> reader(
+ BoxReader::ReadTopLevelBox(&buf[0], buf.size(), &err));
+
+ EXPECT_TRUE(reader->SkipBytes(16) && reader->ScanChildren());
+
+ EmptyBox mpty;
+ EXPECT_TRUE(reader->ReadChild(&mpty));
+ EXPECT_FALSE(reader->ReadChild(&mpty));
+ EXPECT_TRUE(reader->MaybeReadChild(&mpty));
+
+ std::vector<ChildBox> kids;
+
+ EXPECT_TRUE(reader->ReadAllChildren(&kids));
+ EXPECT_EQ(2u, kids.size());
+ kids.clear();
+ EXPECT_FALSE(reader->ReadChildren(&kids));
+ EXPECT_TRUE(reader->MaybeReadChildren(&kids));
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/cenc.cc b/media/mp4/cenc.cc
new file mode 100644
index 0000000..996ebb1
--- /dev/null
+++ b/media/mp4/cenc.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 The Chromium Authors. 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/cenc.h"
+
+#include "media/mp4/box_reader.h"
+#include "media/mp4/rcheck.h"
+
+namespace media {
+namespace mp4 {
+
+FrameCENCInfo::FrameCENCInfo() {}
+FrameCENCInfo::~FrameCENCInfo() {}
+
+bool FrameCENCInfo::Parse(int iv_size, BufferReader* reader) {
+ const int kEntrySize = 6;
+
+ // Mandated by CENC spec
+ RCHECK(iv_size == 8 || iv_size == 16);
+ iv.resize(iv_size);
+
+ uint16 subsample_count;
+ RCHECK(reader->ReadVec(&iv, iv_size) &&
+ reader->Read2(&subsample_count) &&
+ reader->HasBytes(subsample_count * kEntrySize));
+ subsamples.resize(subsample_count);
+
+ for (int i = 0; i < subsample_count; i++) {
+ RCHECK(reader->Read2(&subsamples[i].clear_size) &&
+ reader->Read4(&subsamples[i].encrypted_size));
+ }
+ return true;
+}
+
+size_t FrameCENCInfo::GetTotalSize() const {
+ size_t size = 0;
+ for (size_t i = 0; i < subsamples.size(); i++) {
+ size += subsamples[i].clear_size + subsamples[i].encrypted_size;
+ }
+ return size;
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/cenc.h b/media/mp4/cenc.h
new file mode 100644
index 0000000..ee23743
--- /dev/null
+++ b/media/mp4/cenc.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_CENC_H_
+#define MEDIA_MP4_CENC_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace media {
+namespace mp4 {
+
+class BufferReader;
+
+struct SubsampleSizes {
+ uint16 clear_size;
+ uint32 encrypted_size;
+};
+
+struct FrameCENCInfo {
+ std::vector<uint8> iv;
+ std::vector<SubsampleSizes> subsamples;
+
+ FrameCENCInfo();
+ ~FrameCENCInfo();
+ bool Parse(int iv_size, BufferReader* r);
+ size_t GetTotalSize() const;
+};
+
+
+} // namespace mp4
+} // namespace media
+
+#endif // MEDIA_MP4_CENC_H_
diff --git a/media/mp4/fourccs.h b/media/mp4/fourccs.h
new file mode 100644
index 0000000..8092dda
--- /dev/null
+++ b/media/mp4/fourccs.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_FOURCCS_H_
+#define MEDIA_MP4_FOURCCS_H_
+
+#include <string>
+
+namespace media {
+namespace mp4 {
+
+enum FourCC {
+ FOURCC_NULL = 0,
+ FOURCC_AVC1 = 0x61766331,
+ FOURCC_AVCC = 0x61766343,
+ FOURCC_CENC = 0x63656e63,
+ FOURCC_CO64 = 0x636f3634,
+ FOURCC_CTTS = 0x63747473,
+ FOURCC_DINF = 0x64696e66,
+ FOURCC_EDTS = 0x65647473,
+ FOURCC_ELST = 0x656c7374,
+ FOURCC_ENCA = 0x656e6361,
+ FOURCC_ENCV = 0x656e6376,
+ FOURCC_ESDS = 0x65736473,
+ FOURCC_FREE = 0x66726565,
+ FOURCC_FRMA = 0x66726d61,
+ FOURCC_FTYP = 0x66747970,
+ FOURCC_HDLR = 0x68646c72,
+ FOURCC_HINT = 0x68696e74,
+ FOURCC_IODS = 0x696f6473,
+ FOURCC_MDAT = 0x6d646174,
+ FOURCC_MDHD = 0x6d646864,
+ FOURCC_MDIA = 0x6d646961,
+ FOURCC_MEHD = 0x6d656864,
+ FOURCC_MFHD = 0x6d666864,
+ FOURCC_MFRA = 0x6d667261,
+ FOURCC_MINF = 0x6d696e66,
+ FOURCC_MOOF = 0x6d6f6f66,
+ FOURCC_MOOV = 0x6d6f6f76,
+ FOURCC_MP4A = 0x6d703461,
+ FOURCC_MP4V = 0x6d703476,
+ FOURCC_MVEX = 0x6d766578,
+ FOURCC_MVHD = 0x6d766864,
+ FOURCC_PASP = 0x70617370,
+ FOURCC_PSSH = 0x70737368,
+ FOURCC_SAIO = 0x7361696f,
+ FOURCC_SAIZ = 0x7361697a,
+ FOURCC_SCHI = 0x73636869,
+ FOURCC_SCHM = 0x7363686d,
+ FOURCC_SDTP = 0x73647470,
+ FOURCC_SIDX = 0x73696478,
+ FOURCC_SINF = 0x73696e66,
+ FOURCC_SKIP = 0x736b6970,
+ FOURCC_SMHD = 0x736d6864,
+ FOURCC_SOUN = 0x736f756e,
+ FOURCC_STBL = 0x7374626c,
+ FOURCC_STCO = 0x7374636f,
+ FOURCC_STSC = 0x73747363,
+ FOURCC_STSD = 0x73747364,
+ FOURCC_STSS = 0x73747373,
+ FOURCC_STSZ = 0x7374737a,
+ FOURCC_STTS = 0x73747473,
+ FOURCC_STYP = 0x73747970,
+ FOURCC_TENC = 0x74656e63,
+ FOURCC_TFDT = 0x74666474,
+ FOURCC_TFHD = 0x74666864,
+ FOURCC_TKHD = 0x746b6864,
+ FOURCC_TRAF = 0x74726166,
+ FOURCC_TRAK = 0x7472616b,
+ FOURCC_TREX = 0x74726578,
+ FOURCC_TRUN = 0x7472756e,
+ FOURCC_UDTA = 0x75647461,
+ FOURCC_UUID = 0x75756964,
+ FOURCC_VIDE = 0x76696465,
+ FOURCC_VMHD = 0x766d6864,
+ FOURCC_WIDE = 0x77696465,
+
+ // The following are used for testing
+ FOURCC_CHLD = 0x63686c64,
+ FOURCC_MPTY = 0x6d707479,
+ FOURCC_TEST = 0x74657374,
+};
+
+const inline std::string FourCCToString(FourCC fourcc) {
+ char buf[5];
+ buf[0] = (fourcc >> 24) & 0xff;
+ buf[1] = (fourcc >> 16) & 0xff;
+ buf[2] = (fourcc >> 8) & 0xff;
+ buf[3] = (fourcc) & 0xff;
+ buf[4] = 0;
+ return std::string(buf);
+}
+
+} // namespace mp4
+} // namespace media
+
+#endif // MEDIA_MP4_FOURCCS_H_
diff --git a/media/mp4/mp4_stream_parser.cc b/media/mp4/mp4_stream_parser.cc
new file mode 100644
index 0000000..3d6a190
--- /dev/null
+++ b/media/mp4/mp4_stream_parser.cc
@@ -0,0 +1,341 @@
+// Copyright (c) 2012 The Chromium Authors. 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/mp4_stream_parser.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/time.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/stream_parser_buffer.h"
+#include "media/base/video_decoder_config.h"
+#include "media/mp4/box_definitions.h"
+#include "media/mp4/box_reader.h"
+#include "media/mp4/rcheck.h"
+
+namespace media {
+namespace mp4 {
+
+MP4StreamParser::MP4StreamParser()
+ : state_(kWaitingForInit),
+ moof_head_(0),
+ mdat_tail_(0),
+ has_audio_(false),
+ has_video_(false) {
+}
+
+MP4StreamParser::~MP4StreamParser() {}
+
+void MP4StreamParser::Init(const InitCB& init_cb,
+ const NewConfigCB& config_cb,
+ const NewBuffersCB& audio_cb,
+ const NewBuffersCB& video_cb,
+ const KeyNeededCB& key_needed_cb,
+ const NewMediaSegmentCB& new_segment_cb) {
+ DCHECK_EQ(state_, kWaitingForInit);
+ DCHECK(init_cb_.is_null());
+ DCHECK(!init_cb.is_null());
+ DCHECK(!config_cb.is_null());
+ DCHECK(!audio_cb.is_null() || !video_cb.is_null());
+ DCHECK(!key_needed_cb.is_null());
+
+ ChangeState(kParsingBoxes);
+ init_cb_ = init_cb;
+ config_cb_ = config_cb;
+ audio_cb_ = audio_cb;
+ video_cb_ = video_cb;
+ key_needed_cb_ = key_needed_cb;
+ new_segment_cb_ = new_segment_cb;
+}
+
+void MP4StreamParser::Flush() {
+ DCHECK_NE(state_, kWaitingForInit);
+
+ queue_.Reset();
+ moof_head_ = 0;
+ mdat_tail_ = 0;
+}
+
+bool MP4StreamParser::Parse(const uint8* buf, int size) {
+ DCHECK_NE(state_, kWaitingForInit);
+
+ if (state_ == kError)
+ return false;
+
+ queue_.Push(buf, size);
+
+ BufferQueue audio_buffers;
+ BufferQueue video_buffers;
+
+ bool result, err = false;
+
+ do {
+ if (state_ == kParsingBoxes) {
+ if (mdat_tail_ > queue_.head()) {
+ result = queue_.Trim(mdat_tail_);
+ } else {
+ result = ParseBox(&err);
+ }
+ } else {
+ DCHECK_EQ(kEmittingSamples, state_);
+ result = EnqueueSample(&audio_buffers, &video_buffers, &err);
+ if (result) {
+ int64 max_clear = runs_.GetMaxClearOffset() + moof_head_;
+ DCHECK(max_clear <= queue_.tail());
+ err = !(ReadMDATsUntil(max_clear) && queue_.Trim(max_clear));
+ }
+ }
+ } while (result && !err);
+
+ if (err) {
+ DLOG(ERROR) << "Unknown error while parsing MP4";
+ queue_.Reset();
+ moov_.reset();
+ ChangeState(kError);
+ return false;
+ }
+
+ if (!audio_buffers.empty() &&
+ (audio_cb_.is_null() || !audio_cb_.Run(audio_buffers)))
+ return false;
+ if (!video_buffers.empty() &&
+ (video_cb_.is_null() || !video_cb_.Run(video_buffers)))
+ return false;
+
+ return true;
+}
+
+bool MP4StreamParser::ParseBox(bool* err) {
+ const uint8* buf;
+ int size;
+ queue_.Peek(&buf, &size);
+ if (!size) return false;
+
+ scoped_ptr<BoxReader> reader(BoxReader::ReadTopLevelBox(buf, size, err));
+ if (reader.get() == NULL) return false;
+
+ 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 ParseMDATsUntil()
+ mdat_tail_ = queue_.head() + reader->size();
+ } else {
+ DVLOG(2) << "Skipping unrecognized top-level box: "
+ << FourCCToString(reader->type());
+ }
+
+ queue_.Pop(reader->size());
+ return !(*err);
+}
+
+
+bool MP4StreamParser::ParseMoov(BoxReader* reader) {
+ // TODO(strobe): Respect edit lists.
+ moov_.reset(new Movie);
+
+ RCHECK(moov_->Parse(reader));
+
+ has_audio_ = false;
+ has_video_ = false;
+ parameter_sets_inserted_ = false;
+
+ AudioDecoderConfig audio_config;
+ VideoDecoderConfig video_config;
+
+ for (std::vector<Track>::const_iterator track = moov_->tracks.begin();
+ track != moov_->tracks.end(); ++track) {
+ // TODO(strobe): Only the first audio and video track present in a file are
+ // used. (Track selection is better accomplished via Source IDs, though, so
+ // adding support for track selection within a stream is low-priority.)
+ const SampleDescription& samp_descr =
+ track->media.information.sample_table.description;
+ if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) {
+ RCHECK(!samp_descr.audio_entries.empty());
+ const AudioSampleEntry& entry = samp_descr.audio_entries[0];
+
+ // TODO(strobe): We accept all format values, pending clarification on
+ // the formats used for encrypted media (http://crbug.com/132351).
+ // RCHECK(entry.format == FOURCC_MP4A ||
+ // (entry.format == FOURCC_ENCA &&
+ // entry.sinf.format.format == FOURCC_MP4A));
+
+ const ChannelLayout layout =
+ AVC::ConvertAACChannelCountToChannelLayout(entry.channelcount);
+ audio_config.Initialize(kCodecAAC, entry.samplesize, layout,
+ entry.samplerate, NULL, 0, false);
+ has_audio_ = true;
+ audio_track_id_ = track->header.track_id;
+ }
+ if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) {
+ RCHECK(!samp_descr.video_entries.empty());
+ const VideoSampleEntry& entry = samp_descr.video_entries[0];
+
+ // RCHECK(entry.format == FOURCC_AVC1 ||
+ // (entry.format == FOURCC_ENCV &&
+ // entry.sinf.format.format == FOURCC_AVC1));
+
+ // TODO(strobe): Recover correct crop box and pixel aspect ratio
+ video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12,
+ gfx::Size(entry.width, entry.height),
+ gfx::Rect(0, 0, entry.width, entry.height),
+ // Bogus duration used for framerate, since real
+ // framerate may be variable
+ 1000, track->media.header.timescale,
+ 1, 1,
+ // No decoder-specific buffer needed for AVC;
+ // SPS/PPS are embedded in the video stream
+ NULL, 0, false);
+ has_video_ = true;
+ video_track_id_ = track->header.track_id;
+
+ size_of_nalu_length_ = entry.avcc.length_size;
+ }
+ }
+
+ RCHECK(config_cb_.Run(audio_config, video_config));
+
+ base::TimeDelta duration;
+ if (moov_->extends.header.fragment_duration > 0) {
+ duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration,
+ moov_->header.timescale);
+ } else if (moov_->header.duration > 0) {
+ duration = TimeDeltaFromFrac(moov_->header.duration,
+ moov_->header.timescale);
+ } else {
+ duration = kInfiniteDuration();
+ }
+
+ init_cb_.Run(true, duration);
+ return true;
+}
+
+bool MP4StreamParser::ParseMoof(BoxReader* reader) {
+ RCHECK(moov_.get()); // Must already have initialization segment
+ MovieFragment moof;
+ RCHECK(moof.Parse(reader));
+ RCHECK(runs_.Init(*moov_, moof));
+ new_segment_cb_.Run(runs_.GetMinDecodeTimestamp());
+ ChangeState(kEmittingSamples);
+ return true;
+}
+
+bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
+ BufferQueue* video_buffers,
+ bool* err) {
+ if (!runs_.RunValid()) {
+ ChangeState(kParsingBoxes);
+ return true;
+ }
+
+ if (!runs_.SampleValid()) {
+ runs_.AdvanceRun();
+ return true;
+ }
+
+ DCHECK(!(*err));
+
+ const uint8* buf;
+ int size;
+ queue_.Peek(&buf, &size);
+ if (!size) return false;
+
+ bool audio = has_audio_ && audio_track_id_ == runs_.track_id();
+ bool video = has_video_ && video_track_id_ == runs_.track_id();
+
+ // Skip this entire track if it's not one we're interested in
+ if (!audio && !video) runs_.AdvanceRun();
+
+ // Attempt to cache the auxiliary information first. Aux info is usually
+ // placed in a contiguous block before the sample data, rather than being
+ // interleaved. If we didn't cache it, this would require that we retain the
+ // start of the segment buffer while reading samples. Aux info is typically
+ // quite small compared to sample data, so this pattern is useful on
+ // memory-constrained devices where the source buffer consumes a substantial
+ // portion of the total system memory.
+ if (runs_.NeedsCENC()) {
+ queue_.PeekAt(runs_.cenc_offset() + moof_head_, &buf, &size);
+ return runs_.CacheCENC(buf, size);
+ }
+
+ queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size);
+ if (size < runs_.size()) return false;
+
+ std::vector<uint8> frame_buf(buf, buf + runs_.size());
+ if (video) {
+ RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, &frame_buf));
+ if (!parameter_sets_inserted_) {
+ const AVCDecoderConfigurationRecord* avc_config = NULL;
+ for (size_t t = 0; t < moov_->tracks.size(); t++) {
+ if (moov_->tracks[t].header.track_id == runs_.track_id()) {
+ avc_config = &moov_->tracks[t].media.information.
+ sample_table.description.video_entries[0].avcc;
+ break;
+ }
+ }
+ RCHECK(avc_config != NULL);
+ RCHECK(AVC::InsertParameterSets(*avc_config, &frame_buf));
+ parameter_sets_inserted_ = true;
+ }
+ }
+
+ scoped_refptr<StreamParserBuffer> stream_buf =
+ StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(),
+ runs_.is_keyframe());
+
+ stream_buf->SetDuration(runs_.duration());
+ // We depend on the decoder performing frame reordering without reordering
+ // timestamps, and only provide the decode timestamp in the buffer.
+ stream_buf->SetTimestamp(runs_.dts());
+
+ DVLOG(3) << "Pushing frame: aud=" << audio
+ << ", key=" << runs_.is_keyframe()
+ << ", dur=" << runs_.duration().InMilliseconds()
+ << ", dts=" << runs_.dts().InMilliseconds()
+ << ", size=" << runs_.size();
+
+ if (audio) {
+ audio_buffers->push_back(stream_buf);
+ } else {
+ video_buffers->push_back(stream_buf);
+ }
+
+ runs_.AdvanceSample();
+ return true;
+}
+
+bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) {
+ DCHECK(tgt_offset <= queue_.tail());
+
+ while (mdat_tail_ < tgt_offset) {
+ const uint8* buf;
+ int size;
+ queue_.PeekAt(mdat_tail_, &buf, &size);
+
+ FourCC type;
+ int box_sz;
+ bool err;
+ if (!BoxReader::StartTopLevelBox(buf, size, &type, &box_sz, &err))
+ return false;
+
+ if (type != FOURCC_MDAT) {
+ DLOG(WARNING) << "Unexpected type while parsing MDATs: "
+ << FourCCToString(type);
+ }
+
+ mdat_tail_ += box_sz;
+ }
+
+ return true;
+}
+
+void MP4StreamParser::ChangeState(State new_state) {
+ DVLOG(2) << "Changing state: " << new_state;
+ state_ = new_state;
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/mp4_stream_parser.h b/media/mp4/mp4_stream_parser.h
new file mode 100644
index 0000000..269b9eb0
--- /dev/null
+++ b/media/mp4/mp4_stream_parser.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_MP4_STREAM_PARSER_H_
+#define MEDIA_MP4_MP4_STREAM_PARSER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+#include "media/base/stream_parser.h"
+#include "media/mp4/offset_byte_queue.h"
+#include "media/mp4/track_run_iterator.h"
+
+namespace media {
+namespace mp4 {
+
+struct Movie;
+class BoxReader;
+
+class MEDIA_EXPORT MP4StreamParser : public StreamParser {
+ public:
+ MP4StreamParser();
+ virtual ~MP4StreamParser();
+
+ virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb,
+ const NewBuffersCB& audio_cb,
+ const NewBuffersCB& video_cb,
+ const KeyNeededCB& key_needed_cb,
+ const NewMediaSegmentCB& new_segment_cb) OVERRIDE;
+ virtual void Flush() OVERRIDE;
+ virtual bool Parse(const uint8* buf, int size) OVERRIDE;
+
+ private:
+ enum State {
+ kWaitingForInit,
+ kParsingBoxes,
+ kEmittingSamples,
+ kError
+ };
+
+ bool ParseBox(bool* err);
+ bool ParseMoov(mp4::BoxReader* reader);
+ bool ParseMoof(mp4::BoxReader* reader);
+
+ bool ReadMDATsUntil(const int64 tgt_tail);
+
+ void ChangeState(State new_state);
+
+ bool EmitConfigs();
+ bool EnqueueSample(BufferQueue* audio_buffers,
+ BufferQueue* video_buffers,
+ bool* err);
+
+ State state_;
+ InitCB init_cb_;
+ NewConfigCB config_cb_;
+ NewBuffersCB audio_cb_;
+ NewBuffersCB video_cb_;
+ KeyNeededCB key_needed_cb_;
+ NewMediaSegmentCB new_segment_cb_;
+
+ OffsetByteQueue queue_;
+
+ // These two parameters are only valid in the |kEmittingSegments| state.
+ //
+ // |moof_head_| is the offset of the start of the most recently parsed moof
+ // block. All byte offsets in sample information are relative to this offset,
+ // as mandated by the Media Source spec.
+ int64 moof_head_;
+ // |mdat_tail_| is the stream offset of the end of the current 'mdat' box.
+ // Valid iff it is greater than the head of the queue.
+ int64 mdat_tail_;
+
+ mp4::TrackRunIterator runs_;
+
+ scoped_ptr<mp4::Movie> moov_;
+
+ bool has_audio_;
+ bool has_video_;
+ uint32 audio_track_id_;
+ uint32 video_track_id_;
+ bool parameter_sets_inserted_;
+
+ // We keep this around to avoid having to go digging through the moov with
+ // every frame.
+ uint8 size_of_nalu_length_;
+
+ DISALLOW_COPY_AND_ASSIGN(MP4StreamParser);
+};
+
+} // namespace mp4
+} // namespace media
+
+#endif // MEDIA_MP4_MP4_STREAM_PARSER_H_
diff --git a/media/mp4/mp4_stream_parser_unittest.cc b/media/mp4/mp4_stream_parser_unittest.cc
new file mode 100644
index 0000000..96fd913
--- /dev/null
+++ b/media/mp4/mp4_stream_parser_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/move.h"
+#include "base/time.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/stream_parser_buffer.h"
+#include "media/base/test_data_util.h"
+#include "media/base/video_decoder_config.h"
+#include "media/mp4/mp4_stream_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+namespace media {
+namespace mp4 {
+
+class MP4StreamParserTest : public testing::Test {
+ public:
+ MP4StreamParserTest() : parser_(new MP4StreamParser) {}
+
+ protected:
+ scoped_ptr<MP4StreamParser> parser_;
+
+ bool AppendData(const uint8* data, size_t length) {
+ parser_->Parse(data, length);
+ return true;
+ }
+
+ bool AppendDataInPieces(const uint8* data, size_t length) {
+ return AppendDataInPieces(data, length, 7);
+ }
+
+ bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) {
+ const uint8* start = data;
+ const uint8* end = data + length;
+ while (start < end) {
+ size_t append_size = std::min(piece_size,
+ static_cast<size_t>(end - start));
+ if (!AppendData(start, append_size))
+ return false;
+ start += append_size;
+ }
+ return true;
+ }
+
+ void InitF(bool init_ok, base::TimeDelta duration) {
+ DVLOG(1) << "InitF: ok=" << init_ok
+ << ", dur=" << duration.InMilliseconds();
+ }
+
+ bool NewConfigF(const AudioDecoderConfig& ac,
+ const VideoDecoderConfig& vc) {
+ DVLOG(1) << "NewConfigF: audio=" << ac.IsValidConfig()
+ << ", video=" << vc.IsValidConfig();
+ return true;
+ }
+
+ bool NewBuffersF(const StreamParser::BufferQueue& bufs) {
+ DVLOG(2) << "NewBuffersF: " << bufs.size() << " buffers";
+ for (StreamParser::BufferQueue::const_iterator buf = bufs.begin();
+ buf != bufs.end(); buf++) {
+ DVLOG(3) << " n=" << buf - bufs.begin()
+ << ", size=" << (*buf)->GetDataSize()
+ << ", dur=" << (*buf)->GetDuration().InMilliseconds();
+ }
+ return true;
+ }
+
+ bool KeyNeededF(scoped_array<uint8> init_data, int init_data_size) {
+ DVLOG(1) << "KeyNeededF: " << init_data_size;
+ return true;
+ }
+
+ void NewSegmentF(TimeDelta start_dts) {
+ DVLOG(1) << "NewSegmentF: " << start_dts.InMilliseconds();
+ }
+
+ void InitializeParser() {
+ parser_->Init(
+ base::Bind(&MP4StreamParserTest::InitF, base::Unretained(this)),
+ base::Bind(&MP4StreamParserTest::NewConfigF, base::Unretained(this)),
+ base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)),
+ base::Bind(&MP4StreamParserTest::NewBuffersF, base::Unretained(this)),
+ base::Bind(&MP4StreamParserTest::KeyNeededF, base::Unretained(this)),
+ base::Bind(&MP4StreamParserTest::NewSegmentF, base::Unretained(this)));
+ }
+
+ bool ParseMP4File(const std::string& filename) {
+ InitializeParser();
+
+ scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename);
+ EXPECT_TRUE(AppendDataInPieces(buffer->GetData(),
+ buffer->GetDataSize(),
+ 512));
+ return true;
+ }
+};
+
+TEST_F(MP4StreamParserTest, TestParseBearDASH) {
+ ParseMP4File("bear.1280x720_dash.mp4");
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/offset_byte_queue.cc b/media/mp4/offset_byte_queue.cc
new file mode 100644
index 0000000..862d937
--- /dev/null
+++ b/media/mp4/offset_byte_queue.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 The Chromium Authors. 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/offset_byte_queue.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace media {
+
+OffsetByteQueue::OffsetByteQueue() : buf_(NULL), size_(0), head_(0) {}
+OffsetByteQueue::~OffsetByteQueue() {}
+
+void OffsetByteQueue::Reset() {
+ queue_.Reset();
+ buf_ = NULL;
+ size_ = 0;
+ head_ = 0;
+}
+
+void OffsetByteQueue::Push(const uint8* buf, int size) {
+ queue_.Push(buf, size);
+ Sync();
+ DVLOG(4) << "Buffer pushed. head=" << head() << " tail=" << tail();
+}
+
+void OffsetByteQueue::Peek(const uint8** buf, int* size) {
+ *buf = size_ > 0 ? buf_ : NULL;
+ *size = size_;
+}
+
+void OffsetByteQueue::Pop(int count) {
+ queue_.Pop(count);
+ head_ += count;
+ Sync();
+}
+
+void OffsetByteQueue::PeekAt(int64 offset, const uint8** buf, int* size) {
+ if (offset < head() || offset >= tail()) {
+ *buf = NULL;
+ *size = 0;
+ return;
+ }
+ *buf = &buf_[offset - head()];
+ *size = tail() - offset;
+}
+
+bool OffsetByteQueue::Trim(int64 max_offset) {
+ if (max_offset < head_) return true;
+ if (max_offset > tail()) {
+ Pop(size_);
+ return false;
+ }
+ Pop(max_offset - head_);
+ return true;
+}
+
+void OffsetByteQueue::Sync() {
+ queue_.Peek(&buf_, &size_);
+}
+
+} // namespace media
diff --git a/media/mp4/offset_byte_queue.h b/media/mp4/offset_byte_queue.h
new file mode 100644
index 0000000..9349b96
--- /dev/null
+++ b/media/mp4/offset_byte_queue.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_OFFSET_BYTE_QUEUE_H_
+#define MEDIA_MP4_OFFSET_BYTE_QUEUE_H_
+
+#include "base/basictypes.h"
+#include "media/base/byte_queue.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// A wrapper around a ByteQueue which maintains a notion of a
+// monotonically-increasing offset. All buffer access is done by passing these
+// offsets into this class, going some way towards preventing the proliferation
+// of many different meanings of "offset", "head", etc.
+class MEDIA_EXPORT OffsetByteQueue {
+ public:
+ OffsetByteQueue();
+ ~OffsetByteQueue();
+
+ // These work like their underlying ByteQueue counterparts.
+ void Reset();
+ void Push(const uint8* buf, int size);
+ void Peek(const uint8** buf, int* size);
+ void Pop(int count);
+
+ // Sets |buf| to point at the first buffered byte corresponding to |offset|,
+ // and |size| to the number of bytes available starting from that offset.
+ //
+ // It is an error if the offset is before the current head. It's not an error
+ // if the current offset is beyond tail(), but you will of course get back
+ // a null |buf| and a |size| of zero.
+ void PeekAt(int64 offset, const uint8** buf, int* size);
+
+ // Marks the bytes up to (but not including) |max_offset| as ready for
+ // deletion. This is relatively inexpensive, but will not necessarily reduce
+ // the resident buffer size right away (or ever).
+ //
+ // Returns true if the full range of bytes were successfully trimmed,
+ // including the case where |max_offset| is less than the current head.
+ // Returns false if |max_offset| > tail() (although all bytes currently
+ // buffered are still cleared).
+ bool Trim(int64 max_offset);
+
+ // The head and tail positions, in terms of the file's absolute offsets.
+ // tail() is an exclusive bound.
+ int64 head() { return head_; }
+ int64 tail() { return head_ + size_; }
+
+ private:
+ // Synchronize |buf_| and |size_| with |queue_|.
+ void Sync();
+
+ ByteQueue queue_;
+ const uint8* buf_;
+ int size_;
+ int64 head_;
+
+ DISALLOW_COPY_AND_ASSIGN(OffsetByteQueue);
+};
+
+} // namespace media
+
+#endif // MEDIA_MP4_MP4_STREAM_PARSER_H_
diff --git a/media/mp4/offset_byte_queue_unittest.cc b/media/mp4/offset_byte_queue_unittest.cc
new file mode 100644
index 0000000..3a9b133
--- /dev/null
+++ b/media/mp4/offset_byte_queue_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/mp4/offset_byte_queue.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class OffsetByteQueueTest : public testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE {
+ uint8 buf[256];
+ for (int i = 0; i < 256; i++) {
+ buf[i] = i;
+ }
+ queue_.reset(new OffsetByteQueue);
+ queue_->Push(buf, sizeof(buf));
+ queue_->Push(buf, sizeof(buf));
+ queue_->Pop(384);
+
+ // Queue will start with 128 bytes of data and an offset of 384 bytes.
+ // These values are used throughout the test.
+ }
+
+ protected:
+ scoped_ptr<OffsetByteQueue> queue_;
+};
+
+TEST_F(OffsetByteQueueTest, TestSetUp) {
+ EXPECT_EQ(384, queue_->head());
+ EXPECT_EQ(512, queue_->tail());
+
+ const uint8* buf;
+ int size;
+
+ queue_->Peek(&buf, &size);
+ EXPECT_EQ(128, size);
+ EXPECT_EQ(128, buf[0]);
+ EXPECT_EQ(255, buf[size-1]);
+}
+
+TEST_F(OffsetByteQueueTest, TestPeekAt) {
+ const uint8* buf;
+ int size;
+
+ queue_->PeekAt(128, &buf, &size);
+ EXPECT_EQ(NULL, buf);
+ EXPECT_EQ(0, size);
+
+ queue_->PeekAt(400, &buf, &size);
+ EXPECT_EQ(queue_->tail() - 400, size);
+ EXPECT_EQ(400 - 256, buf[0]);
+
+ queue_->PeekAt(512, &buf, &size);
+ EXPECT_EQ(NULL, buf);
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(OffsetByteQueueTest, TestTrim) {
+ EXPECT_TRUE(queue_->Trim(128));
+ EXPECT_TRUE(queue_->Trim(384));
+ EXPECT_EQ(384, queue_->head());
+ EXPECT_EQ(512, queue_->tail());
+
+ EXPECT_TRUE(queue_->Trim(400));
+ EXPECT_EQ(400, queue_->head());
+ EXPECT_EQ(512, queue_->tail());
+
+ const uint8* buf;
+ int size;
+ queue_->PeekAt(400, &buf, &size);
+ EXPECT_EQ(queue_->tail() - 400, size);
+ EXPECT_EQ(400 - 256, buf[0]);
+
+ // Trimming to the exact end of the buffer should return 'true'. This
+ // accomodates EOS cases.
+ EXPECT_TRUE(queue_->Trim(512));
+ EXPECT_EQ(512, queue_->head());
+ queue_->Peek(&buf, &size);
+ EXPECT_EQ(NULL, buf);
+
+ // Trimming past the end of the buffer should return 'false'; we haven't seen
+ // the preceeding bytes.
+ EXPECT_FALSE(queue_->Trim(513));
+
+ // However, doing that shouldn't affect the EOS case. Only adding new data
+ // should alter this behavior.
+ EXPECT_TRUE(queue_->Trim(512));
+}
+
+} // namespace media
diff --git a/media/mp4/rcheck.h b/media/mp4/rcheck.h
new file mode 100644
index 0000000..8165056
--- /dev/null
+++ b/media/mp4/rcheck.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_RCHECK_H_
+#define MEDIA_MP4_RCHECK_H_
+
+#include "base/logging.h"
+
+#define RCHECK(x) \
+ do { \
+ if (!(x)) { \
+ DLOG(ERROR) << "Failure while parsing MP4: " << #x; \
+ return false; \
+ } \
+ } while (0)
+
+#endif // MEDIA_MP4_RCHECK_H_
diff --git a/media/mp4/track_run_iterator.cc b/media/mp4/track_run_iterator.cc
new file mode 100644
index 0000000..8ec05e2
--- /dev/null
+++ b/media/mp4/track_run_iterator.cc
@@ -0,0 +1,263 @@
+// Copyright (c) 2012 The Chromium Authors. 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/track_run_iterator.h"
+
+#include <algorithm>
+
+#include "media/base/stream_parser_buffer.h"
+#include "media/mp4/rcheck.h"
+
+namespace media {
+namespace mp4 {
+
+base::TimeDelta TimeDeltaFromFrac(int64 numer, uint64 denom) {
+ DCHECK_LT((numer > 0 ? numer : -numer),
+ kint64max / base::Time::kMicrosecondsPerSecond);
+ return base::TimeDelta::FromMicroseconds(
+ base::Time::kMicrosecondsPerSecond * numer / denom);
+}
+
+static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
+
+TrackRunInfo::TrackRunInfo() {}
+TrackRunInfo::~TrackRunInfo() {}
+
+TrackRunIterator::TrackRunIterator() {}
+TrackRunIterator::~TrackRunIterator() {}
+
+static void PopulateSampleInfo(const Track& trak,
+ const TrackExtends& trex,
+ const TrackFragmentHeader& tfhd,
+ const TrackFragmentRun& trun,
+ const uint32 i,
+ SampleInfo* sample_info) {
+ if (i < trun.sample_sizes.size()) {
+ sample_info->size = trun.sample_sizes[i];
+ } else if (tfhd.default_sample_size > 0) {
+ sample_info->size = tfhd.default_sample_size;
+ } else {
+ sample_info->size = trex.default_sample_size;
+ }
+
+ const uint64 timescale = trak.media.header.timescale;
+ uint64 duration;
+ if (i < trun.sample_durations.size()) {
+ duration = trun.sample_durations[i];
+ } else if (tfhd.default_sample_duration > 0) {
+ duration = tfhd.default_sample_duration;
+ } else {
+ duration = trex.default_sample_duration;
+ }
+ sample_info->duration = TimeDeltaFromFrac(duration, timescale);
+
+ if (i < trun.sample_composition_time_offsets.size()) {
+ sample_info->cts_offset =
+ TimeDeltaFromFrac(trun.sample_composition_time_offsets[i], timescale);
+ } else {
+ sample_info->cts_offset = TimeDelta::FromMicroseconds(0);
+ }
+
+ uint32 flags;
+ if (i < trun.sample_flags.size()) {
+ flags = trun.sample_flags[i];
+ } else if (tfhd.has_default_sample_flags) {
+ flags = tfhd.default_sample_flags;
+ } else {
+ flags = trex.default_sample_flags;
+ }
+ sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask);
+}
+
+class CompareOffset {
+ public:
+ bool operator()(TrackRunInfo a, TrackRunInfo b) {
+ int64 a_min = a.sample_start_offset;
+ if (a.is_encrypted && a.cenc_start_offset < a_min)
+ a_min = a.cenc_start_offset;
+ int64 b_min = b.sample_start_offset;
+ if (b.is_encrypted && b.cenc_start_offset < b_min)
+ b_min = b.cenc_start_offset;
+ return a_min < b_min;
+ }
+};
+
+bool TrackRunIterator::Init(const Movie& moov, const MovieFragment& moof) {
+ runs_.clear();
+
+ for (size_t i = 0; i < moof.tracks.size(); i++) {
+ const TrackFragment& traf = moof.tracks[i];
+
+ const Track* trak = NULL;
+ for (size_t t = 0; t < moov.tracks.size(); t++) {
+ if (moov.tracks[t].header.track_id == traf.header.track_id)
+ trak = &moov.tracks[t];
+ }
+
+ const TrackExtends* trex = NULL;
+ for (size_t t = 0; t < moov.extends.tracks.size(); t++) {
+ if (moov.extends.tracks[t].track_id == traf.header.track_id)
+ trex = &moov.extends.tracks[t];
+ }
+ RCHECK(trak && trex);
+
+ const ProtectionSchemeInfo* sinf = NULL;
+ const SampleDescription& stsd =
+ trak->media.information.sample_table.description;
+ if (stsd.type == kAudio) {
+ sinf = &stsd.audio_entries[0].sinf;
+ } else if (stsd.type == kVideo) {
+ sinf = &stsd.video_entries[0].sinf;
+ } else {
+ DVLOG(1) << "Skipping unhandled track type";
+ continue;
+ }
+
+ for (size_t j = 0; j < traf.runs.size(); j++) {
+ const TrackFragmentRun& trun = traf.runs[j];
+ TrackRunInfo tri;
+ tri.track_id = traf.header.track_id;
+ tri.start_dts = TimeDeltaFromFrac(traf.decode_time.decode_time,
+ trak->media.header.timescale);
+ tri.sample_start_offset = trun.data_offset;
+ tri.is_encrypted = sinf->info.track_encryption.is_encrypted;
+ // TODO(strobe): CENC recovery and testing (http://crbug.com/132351)
+
+ tri.samples.resize(trun.sample_count);
+
+ for (size_t k = 0; k < trun.sample_count; k++) {
+ PopulateSampleInfo(*trak, *trex, traf.header, trun, k, &tri.samples[k]);
+ }
+ runs_.push_back(tri);
+ }
+ }
+
+ std::sort(runs_.begin(), runs_.end(), CompareOffset());
+ run_itr_ = runs_.begin();
+ min_clear_offset_itr_ = min_clear_offsets_.begin();
+ ResetRun();
+ return true;
+}
+
+void TrackRunIterator::AdvanceRun() {
+ ++run_itr_;
+ if (min_clear_offset_itr_ != min_clear_offsets_.end())
+ ++min_clear_offset_itr_;
+ ResetRun();
+}
+
+void TrackRunIterator::ResetRun() {
+ if (!RunValid()) return;
+ sample_dts_ = run_itr_->start_dts;
+ sample_offset_ = run_itr_->sample_start_offset;
+ sample_itr_ = run_itr_->samples.begin();
+}
+
+void TrackRunIterator::AdvanceSample() {
+ DCHECK(SampleValid());
+ sample_dts_ += sample_itr_->duration;
+ sample_offset_ += sample_itr_->size;
+ ++sample_itr_;
+}
+
+bool TrackRunIterator::NeedsCENC() {
+ CHECK(!is_encrypted()) << "TODO(strobe): Implement CENC.";
+ return is_encrypted();
+}
+
+bool TrackRunIterator::CacheCENC(const uint8* buf, int size) {
+ LOG(FATAL) << "Not implemented";
+ return false;
+}
+
+bool TrackRunIterator::RunValid() const {
+ return run_itr_ != runs_.end();
+}
+
+bool TrackRunIterator::SampleValid() const {
+ return RunValid() && (sample_itr_ != run_itr_->samples.end());
+}
+
+int64 TrackRunIterator::GetMaxClearOffset() {
+ int64 offset = kint64max;
+
+ if (SampleValid()) {
+ offset = std::min(offset, sample_offset_);
+ if (NeedsCENC()) {
+ offset = std::min(offset, cenc_offset());
+ }
+ }
+ if (min_clear_offset_itr_ != min_clear_offsets_.end()) {
+ offset = std::min(offset, *min_clear_offset_itr_);
+ }
+ if (offset == kint64max) return 0;
+ return offset;
+}
+
+TimeDelta TrackRunIterator::GetMinDecodeTimestamp() {
+ TimeDelta dts = kInfiniteDuration();
+ for (size_t i = 0; i < runs_.size(); i++) {
+ if (runs_[i].start_dts < dts)
+ dts = runs_[i].start_dts;
+ }
+ return dts;
+}
+
+uint32 TrackRunIterator::track_id() const {
+ DCHECK(RunValid());
+ return run_itr_->track_id;
+}
+
+bool TrackRunIterator::is_encrypted() const {
+ DCHECK(RunValid());
+ return run_itr_->is_encrypted;
+}
+
+int64 TrackRunIterator::cenc_offset() const {
+ DCHECK(is_encrypted());
+ return run_itr_->cenc_start_offset;
+}
+
+int TrackRunIterator::cenc_size() const {
+ DCHECK(is_encrypted());
+ return run_itr_->cenc_total_size;
+}
+
+int64 TrackRunIterator::offset() const {
+ DCHECK(SampleValid());
+ return sample_offset_;
+}
+
+int TrackRunIterator::size() const {
+ DCHECK(SampleValid());
+ return sample_itr_->size;
+}
+
+TimeDelta TrackRunIterator::dts() const {
+ DCHECK(SampleValid());
+ return sample_dts_;
+}
+
+TimeDelta TrackRunIterator::cts() const {
+ DCHECK(SampleValid());
+ return sample_dts_ + sample_itr_->cts_offset;
+}
+
+TimeDelta TrackRunIterator::duration() const {
+ DCHECK(SampleValid());
+ return sample_itr_->duration;
+}
+
+bool TrackRunIterator::is_keyframe() const {
+ DCHECK(SampleValid());
+ return sample_itr_->is_keyframe;
+}
+
+const FrameCENCInfo& TrackRunIterator::frame_cenc_info() {
+ DCHECK(is_encrypted());
+ return frame_cenc_info_;
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/mp4/track_run_iterator.h b/media/mp4/track_run_iterator.h
new file mode 100644
index 0000000..c1f61c3
--- /dev/null
+++ b/media/mp4/track_run_iterator.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MP4_TRACK_RUN_ITERATOR_H_
+#define MEDIA_MP4_TRACK_RUN_ITERATOR_H_
+
+#include <vector>
+
+#include "base/time.h"
+#include "media/mp4/box_definitions.h"
+#include "media/mp4/cenc.h"
+
+namespace media {
+namespace mp4 {
+
+using base::TimeDelta;
+
+base::TimeDelta TimeDeltaFromFrac(int64 numer, uint64 denom);
+
+struct SampleInfo {
+ int size;
+ TimeDelta duration;
+ TimeDelta cts_offset;
+ bool is_keyframe;
+};
+
+struct TrackRunInfo {
+ uint32 track_id;
+ std::vector<SampleInfo> samples;
+ TimeDelta start_dts;
+ int64 sample_start_offset;
+
+ bool is_encrypted;
+ int64 cenc_start_offset;
+ int cenc_total_size;
+ uint8 default_cenc_size;
+ // Only valid if default_cenc_size == 0. (In this case, infer the sample count
+ // from the 'samples' parameter; they shall be the same.)
+ std::vector<uint8> cenc_sizes;
+
+ TrackRunInfo();
+ ~TrackRunInfo();
+};
+
+class TrackRunIterator {
+ public:
+ TrackRunIterator();
+ ~TrackRunIterator();
+
+ void Reset();
+
+ // Sets up the iterator to handle all the runs from the current fragment.
+ bool Init(const Movie& moov, const MovieFragment& moof);
+
+ // Returns true if the properties of the current run or sample are valid.
+ bool RunValid() const;
+ bool SampleValid() const;
+
+ // Advance the properties to refer to the next run or sample. Requires that
+ // the current sample be valid.
+ void AdvanceRun();
+ void AdvanceSample();
+
+ // Returns true iff the track is encrypted and the common encryption sample
+ // auxiliary information has not yet been cached for the current track.
+ bool NeedsCENC();
+
+ // Cache the CENC data from the given buffer.
+ bool CacheCENC(const uint8* buf, int size);
+
+ // Returns the maximum buffer location at which no data earlier in the stream
+ // will be required in order to read the current or any subsequent sample. You
+ // may clear all data up to this offset before reading the current sample
+ // safely. Result is in the same units as offset() (for Media Source this is
+ // in bytes past the the head of the MOOF box).
+ int64 GetMaxClearOffset();
+
+ // Returns the minimum timestamp (or kInfiniteDuration if no runs present).
+ TimeDelta GetMinDecodeTimestamp();
+
+ // Property of the current run. Reqiures RunValid().
+ uint32 track_id() const;
+ bool is_encrypted() const;
+ // Only valid if is_encrypted() is true.
+ int64 cenc_offset() const;
+ int cenc_size() const;
+
+ // Properties of the current sample. Requires SampleValid().
+ int64 offset() const;
+ int size() const;
+ TimeDelta dts() const;
+ TimeDelta cts() const;
+ TimeDelta duration() const;
+ bool is_keyframe() const;
+ // Only valid if is_encrypted() is true and NeedsCENC() is false.
+ const FrameCENCInfo& frame_cenc_info();
+
+ private:
+ void ResetRun();
+ std::vector<TrackRunInfo> runs_;
+ std::vector<TrackRunInfo>::const_iterator run_itr_;
+
+ std::vector<SampleInfo>::const_iterator sample_itr_;
+ std::vector<uint8> cenc_cache_;
+
+ std::vector<int64> min_clear_offsets_;
+ std::vector<int64>::const_iterator min_clear_offset_itr_;
+
+ TimeDelta sample_dts_;
+ int64 sample_offset_;
+ FrameCENCInfo frame_cenc_info_;
+};
+
+} // namespace mp4
+} // namespace media
+
+#endif // MEDIA_MP4_TRACK_RUN_ITERATOR_H_