diff options
-rw-r--r-- | media/filters/chunk_demuxer.cc | 23 | ||||
-rw-r--r-- | media/filters/ffmpeg_audio_decoder.cc | 4 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder.cc | 4 | ||||
-rw-r--r-- | media/media.gyp | 19 | ||||
-rw-r--r-- | media/mp4/avc.cc | 114 | ||||
-rw-r--r-- | media/mp4/avc.h | 34 | ||||
-rw-r--r-- | media/mp4/avc_unittest.cc | 96 | ||||
-rw-r--r-- | media/mp4/box_definitions.cc | 638 | ||||
-rw-r--r-- | media/mp4/box_definitions.h | 330 | ||||
-rw-r--r-- | media/mp4/box_reader.cc | 193 | ||||
-rw-r--r-- | media/mp4/box_reader.h | 201 | ||||
-rw-r--r-- | media/mp4/box_reader_unittest.cc | 182 | ||||
-rw-r--r-- | media/mp4/cenc.cc | 45 | ||||
-rw-r--r-- | media/mp4/cenc.h | 36 | ||||
-rw-r--r-- | media/mp4/fourccs.h | 98 | ||||
-rw-r--r-- | media/mp4/mp4_stream_parser.cc | 341 | ||||
-rw-r--r-- | media/mp4/mp4_stream_parser.h | 97 | ||||
-rw-r--r-- | media/mp4/mp4_stream_parser_unittest.cc | 114 | ||||
-rw-r--r-- | media/mp4/offset_byte_queue.cc | 63 | ||||
-rw-r--r-- | media/mp4/offset_byte_queue.h | 66 | ||||
-rw-r--r-- | media/mp4/offset_byte_queue_unittest.cc | 96 | ||||
-rw-r--r-- | media/mp4/rcheck.h | 18 | ||||
-rw-r--r-- | media/mp4/track_run_iterator.cc | 263 | ||||
-rw-r--r-- | media/mp4/track_run_iterator.h | 118 |
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(×cale) && + reader->Read8(&duration)); + } else { + RCHECK(reader->Read4Into8(&creation_time) && + reader->Read4Into8(&modification_time) && + reader->Read4(×cale) && + 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(×cale) && + reader->Read8(&duration)); + } else { + RCHECK(reader->Read4Into8(&creation_time) && + reader->Read4Into8(&modification_time) && + reader->Read4(×cale) && + 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_ |