diff --git a/packager/media/base/media_base.gyp b/packager/media/base/media_base.gyp index d630676a09..d4a0fbbed2 100644 --- a/packager/media/base/media_base.gyp +++ b/packager/media/base/media_base.gyp @@ -61,6 +61,8 @@ 'offset_byte_queue.cc', 'offset_byte_queue.h', 'producer_consumer_queue.h', + 'protection_system_specific_info.cc', + 'protection_system_specific_info.h', 'request_signer.cc', 'request_signer.h', 'rsa_key.cc', @@ -116,6 +118,7 @@ 'muxer_util_unittest.cc', 'offset_byte_queue_unittest.cc', 'producer_consumer_queue_unittest.cc', + 'protection_system_specific_info_unittest.cc', 'rsa_key_unittest.cc', 'status_test_util_unittest.cc', 'status_unittest.cc', diff --git a/packager/media/base/protection_system_specific_info.cc b/packager/media/base/protection_system_specific_info.cc new file mode 100644 index 0000000000..737c0b4612 --- /dev/null +++ b/packager/media/base/protection_system_specific_info.cc @@ -0,0 +1,119 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/base/protection_system_specific_info.h" + +#include "packager/media/base/buffer_writer.h" +// Header-only files: +#include "packager/media/formats/mp4/fourccs.h" +#include "packager/media/formats/mp4/rcheck.h" + +namespace edash_packager { +namespace media { + +namespace { +const size_t kSystemIdSize = 16u; +const size_t kKeyIdSize = 16u; +} // namespace + +ProtectionSystemSpecificInfo::ProtectionSystemSpecificInfo() + : version_(0) {} +ProtectionSystemSpecificInfo::~ProtectionSystemSpecificInfo() {} + +bool ProtectionSystemSpecificInfo::ParseBoxes( + const uint8_t* data, + size_t data_size, + std::vector* pssh_boxes) { + BufferReader reader(data, data_size); + while (reader.HasBytes(1)) { + size_t start_position = reader.pos(); + uint32_t size; + RCHECK(reader.Read4(&size)); + RCHECK(reader.SkipBytes(size - 4)); + + pssh_boxes->emplace_back(); + RCHECK(pssh_boxes->back().Parse(data + start_position, size)); + } + + return true; +} + +bool ProtectionSystemSpecificInfo::Parse(const uint8_t* data, + size_t data_size) { + uint32_t size; + uint32_t box_type; + uint32_t version_and_flags; + BufferReader reader(data, data_size); + + RCHECK(reader.Read4(&size)); + RCHECK(reader.Read4(&box_type)); + RCHECK(size == data_size); + RCHECK(box_type == mp4::FOURCC_PSSH); + RCHECK(reader.Read4(&version_and_flags)); + + version_ = (version_and_flags >> 24); + RCHECK(version_ < 2); + RCHECK(reader.ReadToVector(&system_id_, kSystemIdSize)); + + if (version_ == 1) { + uint32_t key_id_count; + RCHECK(reader.Read4(&key_id_count)); + + key_ids_.resize(key_id_count); + for (uint32_t i = 0; i < key_id_count; i++) { + RCHECK(reader.ReadToVector(&key_ids_[i], kKeyIdSize)); + } + } + + // TODO: Consider parsing key IDs from Widevine PSSH data. + uint32_t pssh_data_size; + RCHECK(reader.Read4(&pssh_data_size)); + RCHECK(reader.ReadToVector(&pssh_data_, pssh_data_size)); + + // We should be at the end of the data. The reader should be initialized to + // the data and size according to the size field of the box; therefore it + // is an error if there are bytes remaining. + RCHECK(!reader.HasBytes(1)); + return true; +} + +std::vector ProtectionSystemSpecificInfo::CreateBox() const { + DCHECK_EQ(kSystemIdSize, system_id_.size()); + + const uint32_t box_type = mp4::FOURCC_PSSH; + const uint32_t version_and_flags = (static_cast(version_) << 24); + const uint32_t pssh_data_size = pssh_data_.size(); + + const uint32_t key_id_count = key_ids_.size(); + const uint32_t key_ids_size = + sizeof(key_id_count) + kKeyIdSize * key_id_count; + const uint32_t extra_size = version_ == 1 ? key_ids_size : 0; + + const uint32_t total_size = + sizeof(total_size) + sizeof(box_type) + sizeof(version_and_flags) + + kSystemIdSize + extra_size + sizeof(pssh_data_size) + pssh_data_size; + + BufferWriter writer; + writer.AppendInt(total_size); + writer.AppendInt(box_type); + writer.AppendInt(version_and_flags); + writer.AppendVector(system_id_); + if (version_ == 1) { + writer.AppendInt(key_id_count); + for (size_t i = 0; i < key_id_count; i++) { + DCHECK_EQ(kKeyIdSize, key_ids_[i].size()); + writer.AppendVector(key_ids_[i]); + } + } + writer.AppendInt(pssh_data_size); + writer.AppendVector(pssh_data_); + + DCHECK_EQ(total_size, writer.Size()); + return std::vector(writer.Buffer(), writer.Buffer() + writer.Size()); +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/base/protection_system_specific_info.h b/packager/media/base/protection_system_specific_info.h new file mode 100644 index 0000000000..97827be056 --- /dev/null +++ b/packager/media/base/protection_system_specific_info.h @@ -0,0 +1,75 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_BASE_PSSH_H_ +#define MEDIA_BASE_PSSH_H_ + +#include +#include + +#include "packager/base/logging.h" +#include "packager/media/base/buffer_reader.h" + +namespace edash_packager { +namespace media { + +class ProtectionSystemSpecificInfo { + public: + ProtectionSystemSpecificInfo(); + ~ProtectionSystemSpecificInfo(); + + /// Parses multiple PSSH boxes from @a data. These boxes should be + /// concatenated together. Any non-PSSH box is an error. + /// @return true on success; false on failure. + static bool ParseBoxes( + const uint8_t* data, + size_t data_size, + std::vector* pssh_boxes); + + /// Parses the given PSSH box into this object. + /// @return true on success; false on failure. + bool Parse(const uint8_t* data, size_t data_size); + + /// Creates a PSSH box for the current data. + std::vector CreateBox() const; + + uint8_t version() const { return version_; } + const std::vector& system_id() const { return system_id_; } + const std::vector>& key_ids() const { return key_ids_; } + const std::vector& pssh_data() const { return pssh_data_; } + + void set_version(uint8_t version) { + DCHECK_LT(version, 2); + version_ = version; + } + void set_system_id(const std::vector& system_id) { + DCHECK_EQ(16u, system_id.size()); + system_id_ = system_id; + } + void add_key_id(const std::vector& key_id) { + DCHECK_EQ(16u, key_id.size()); + key_ids_.push_back(key_id); + } + void clear_key_ids() { key_ids_.clear(); } + void set_pssh_data(const std::vector& pssh_data) { + pssh_data_ = pssh_data; + } + + private: + uint8_t version_; + std::vector system_id_; + std::vector> key_ids_; + std::vector pssh_data_; + + // Don't use DISALLOW_COPY_AND_ASSIGN since the data stored here should be + // small, so the performance impact should be minimal. +}; + +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_BASE_PSSH_H_ + diff --git a/packager/media/base/protection_system_specific_info_unittest.cc b/packager/media/base/protection_system_specific_info_unittest.cc new file mode 100644 index 0000000000..6ce3154945 --- /dev/null +++ b/packager/media/base/protection_system_specific_info_unittest.cc @@ -0,0 +1,140 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include + +#include "packager/base/macros.h" +#include "packager/media/base/protection_system_specific_info.h" + +namespace edash_packager { +namespace media { + +namespace { +const uint8_t kV0BoxArray[] = { + 0x00, 0x00, 0x00, 0x21, 'p', 's', 's', 'h', // Header + 0x00, 0x00, 0x00, 0x00, // Version = 0, flags = 0 + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, // System ID + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0x00, 0x00, 0x00, 0x01, // Data size(1) + 0xFF +}; +const uint8_t kV1BoxArray[] = { + 0x00, 0x00, 0x00, 0x35, 'p', 's', 's', 'h', // Header + 0x01, 0x00, 0x00, 0x00, // Version = 1, flags = 0 + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, // System ID + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0x00, 0x00, 0x00, 0x01, // KID_count(1) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // First KID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, // Data size(1) + 0xFF +}; + +const uint8_t kTestSystemIdArray[] = { + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, // System ID + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, +}; +const uint8_t kTestKeyIdArray[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // First KID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +const uint8_t kTestPsshDataArray[] = {0xFF}; +} // namespace + +class PsshTest : public ::testing::Test { + public: + PsshTest() + : v0_box_(kV0BoxArray, kV0BoxArray + arraysize(kV0BoxArray)), + v1_box_(kV1BoxArray, kV1BoxArray + arraysize(kV1BoxArray)), + test_system_id_(kTestSystemIdArray, + kTestSystemIdArray + arraysize(kTestSystemIdArray)), + test_key_id_(kTestKeyIdArray, + kTestKeyIdArray + arraysize(kTestKeyIdArray)), + test_pssh_data_(kTestPsshDataArray, + kTestPsshDataArray + arraysize(kTestPsshDataArray)) {} + + const std::vector v0_box_; + const std::vector v1_box_; + const std::vector test_system_id_; + const std::vector test_key_id_; + const std::vector test_pssh_data_; +}; + +TEST_F(PsshTest, ParseBoxes_SupportsV0) { + std::vector info; + ASSERT_TRUE(ProtectionSystemSpecificInfo::ParseBoxes( + v0_box_.data(), v0_box_.size(), &info)); + ASSERT_EQ(1u, info.size()); + + ASSERT_EQ(0u, info[0].key_ids().size()); + EXPECT_EQ(test_system_id_, info[0].system_id()); + EXPECT_EQ(test_pssh_data_, info[0].pssh_data()); + EXPECT_EQ(0, info[0].version()); +} + +TEST_F(PsshTest, ParseBoxes_SupportsV1) { + std::vector info; + ASSERT_TRUE(ProtectionSystemSpecificInfo::ParseBoxes( + v1_box_.data(), v1_box_.size(), &info)); + ASSERT_EQ(1u, info.size()); + + ASSERT_EQ(1u, info[0].key_ids().size()); + EXPECT_EQ(test_system_id_, info[0].system_id()); + EXPECT_EQ(test_key_id_, info[0].key_ids()[0]); + EXPECT_EQ(test_pssh_data_, info[0].pssh_data()); + EXPECT_EQ(1, info[0].version()); +} + +TEST_F(PsshTest, ParseBoxes_SupportsConcatenatedBoxes) { + std::vector data; + data.insert(data.end(), v1_box_.begin(), v1_box_.end()); + data.insert(data.end(), v0_box_.begin(), v0_box_.end()); + data.insert(data.end(), v1_box_.begin(), v1_box_.end()); + + std::vector info; + ASSERT_TRUE(ProtectionSystemSpecificInfo::ParseBoxes(data.data(), + data.size(), &info)); + ASSERT_EQ(3u, info.size()); + + ASSERT_EQ(1u, info[0].key_ids().size()); + EXPECT_EQ(test_system_id_, info[0].system_id()); + EXPECT_EQ(test_key_id_, info[0].key_ids()[0]); + EXPECT_EQ(test_pssh_data_, info[0].pssh_data()); + EXPECT_EQ(1, info[0].version()); + + ASSERT_EQ(0u, info[1].key_ids().size()); + EXPECT_EQ(test_system_id_, info[1].system_id()); + EXPECT_EQ(test_pssh_data_, info[1].pssh_data()); + EXPECT_EQ(0, info[1].version()); + + ASSERT_EQ(1u, info[2].key_ids().size()); + EXPECT_EQ(test_system_id_, info[2].system_id()); + EXPECT_EQ(test_key_id_, info[2].key_ids()[0]); + EXPECT_EQ(test_pssh_data_, info[2].pssh_data()); + EXPECT_EQ(1, info[2].version()); +} + +TEST_F(PsshTest, CreateBox_MakesV0Boxes) { + ProtectionSystemSpecificInfo info; + info.set_system_id(test_system_id_); + info.set_pssh_data(test_pssh_data_); + info.set_version(0); + + EXPECT_EQ(v0_box_, info.CreateBox()); +} + +TEST_F(PsshTest, CreateBox_MakesV1Boxes) { + ProtectionSystemSpecificInfo info; + info.add_key_id(test_key_id_); + info.set_system_id(test_system_id_); + info.set_pssh_data(test_pssh_data_); + info.set_version(1); + + EXPECT_EQ(v1_box_, info.CreateBox()); +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 225390a51d..3afe3c4d43 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -158,35 +158,20 @@ ProtectionSystemSpecificHeader::~ProtectionSystemSpecificHeader() {} FourCC ProtectionSystemSpecificHeader::BoxType() const { return FOURCC_PSSH; } bool ProtectionSystemSpecificHeader::ReadWriteInternal(BoxBuffer* buffer) { - if (!buffer->Reading() && !raw_box.empty()) { - // Write the raw box directly. - buffer->writer()->AppendVector(raw_box); - return true; - } - - uint32_t size = data.size(); - RCHECK(ReadWriteHeaderInternal(buffer) && - buffer->ReadWriteVector(&system_id, 16) && - buffer->ReadWriteUInt32(&size) && - buffer->ReadWriteVector(&data, size)); - if (buffer->Reading()) { - // Copy the entire box, including the header, for passing to EME as - // initData. - DCHECK(raw_box.empty()); BoxReader* reader = buffer->reader(); DCHECK(reader); raw_box.assign(reader->data(), reader->data() + reader->size()); + } else { + DCHECK(!raw_box.empty()); + buffer->writer()->AppendVector(raw_box); } + return true; } uint32_t ProtectionSystemSpecificHeader::ComputeSizeInternal() { - if (!raw_box.empty()) { - return raw_box.size(); - } else { - return HeaderSize() + system_id.size() + sizeof(uint32_t) + data.size(); - } + return raw_box.size(); } SampleAuxiliaryInformationOffset::SampleAuxiliaryInformationOffset() {} diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index b1e2bfb04a..a1ce10dcba 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -57,8 +57,6 @@ struct SegmentType : FileType { struct ProtectionSystemSpecificHeader : FullBox { DECLARE_BOX_METHODS(ProtectionSystemSpecificHeader); - std::vector system_id; - std::vector data; std::vector raw_box; }; diff --git a/packager/media/formats/mp4/box_definitions_comparison.h b/packager/media/formats/mp4/box_definitions_comparison.h index ab4c45dbd8..7afb92315f 100644 --- a/packager/media/formats/mp4/box_definitions_comparison.h +++ b/packager/media/formats/mp4/box_definitions_comparison.h @@ -29,7 +29,7 @@ inline bool operator==(const FileType& lhs, const FileType& rhs) { inline bool operator==(const ProtectionSystemSpecificHeader& lhs, const ProtectionSystemSpecificHeader& rhs) { - return lhs.system_id == rhs.system_id && lhs.data == rhs.data; + return lhs.raw_box == rhs.raw_box; } inline bool operator==(const SampleAuxiliaryInformationOffset& lhs, diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index 5cce1e8dba..3610a1fd03 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -9,6 +9,7 @@ #include #include "packager/base/memory/scoped_ptr.h" +#include "packager/media/base/protection_system_specific_info.h" #include "packager/media/base/buffer_writer.h" #include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/box_definitions_comparison.h" @@ -25,6 +26,9 @@ const uint8_t kData8[] = {1, 8, 42, 98, 156}; const uint16_t kData16[] = {1, 15, 45, 768, 60000}; const uint32_t kData32[] = {1, 24, 99, 1234, 9000000}; const uint64_t kData64[] = {1, 9000000, 12345678901234ULL, 56780909090900ULL}; +const uint8_t kPsshBox[] = {0, 0, 0, 0x22, 'p', 's', 's', 'h', 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, 0xf0, 0x00}; const TrackType kSampleDescriptionTrackType = kVideo; // 4-byte FourCC + 4-bytes size. @@ -143,14 +147,11 @@ class BoxDefinitionsTestGeneral : public testing::Test { } void Fill(ProtectionSystemSpecificHeader* pssh) { - pssh->system_id.assign(kData16Bytes, - kData16Bytes + arraysize(kData16Bytes)); - pssh->data.assign(kData8, kData8 + arraysize(kData8)); + pssh->raw_box.assign(kPsshBox, kPsshBox + arraysize(kPsshBox)); } void Modify(ProtectionSystemSpecificHeader* pssh) { - pssh->system_id[2] *= 3; - pssh->data.assign(kData4, kData4 + arraysize(kData4)); + pssh->raw_box[32] *= 3; } void Fill(SampleAuxiliaryInformationOffset* saio) { @@ -918,6 +919,7 @@ class BoxDefinitionsTestGeneral : public testing::Test { bool IsOptional(const AC3Specific* box) { return true; } bool IsOptional(const EC3Specific* box) { return true; } // Recommended, but optional. + bool IsOptional(const ProtectionSystemSpecificHeader* box) { return true; } bool IsOptional(const WebVTTSourceLabelBox* box) { return true; } bool IsOptional(const CompositionTimeToSample* box) { return true; } bool IsOptional(const SyncSample* box) { return true; } @@ -1136,26 +1138,6 @@ TEST_F(BoxDefinitionsTest, EC3SampleEntry) { ASSERT_EQ(entry, entry_readback); } -TEST_F(BoxDefinitionsTest, ProtectionSystemSpecificHeader) { - ProtectionSystemSpecificHeader pssh; - Fill(&pssh); - pssh.Write(this->buffer_.get()); - - ProtectionSystemSpecificHeader pssh_readback; - ASSERT_TRUE(ReadBack(&pssh_readback)); - ASSERT_EQ(pssh, pssh_readback); - - pssh_readback.raw_box[15] += 1; - pssh_readback.Write(this->buffer_.get()); - - ProtectionSystemSpecificHeader pssh_readback2; - ASSERT_TRUE(ReadBack(&pssh_readback2)); - - // If raw_box is set, raw_box will be written instead. - ASSERT_FALSE(pssh_readback == pssh_readback2); - ASSERT_EQ(pssh_readback.raw_box, pssh_readback2.raw_box); -} - TEST_F(BoxDefinitionsTest, CompactSampleSize_FieldSize16) { CompactSampleSize stz2; stz2.field_size = 16; diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index ffe5cc294b..96537949c6 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -17,6 +17,7 @@ #include "packager/media/base/key_source.h" #include "packager/media/base/macros.h" #include "packager/media/base/media_sample.h" +#include "packager/media/base/protection_system_specific_info.h" #include "packager/media/base/video_stream_info.h" #include "packager/media/file/file.h" #include "packager/media/file/file_closer.h" @@ -584,8 +585,10 @@ bool MP4MediaParser::FetchKeysIfNecessary( base::HexStringToBytes(kWidevineKeySystemId, &widevine_system_id); for (std::vector::const_iterator iter = headers.begin(); iter != headers.end(); ++iter) { - if (iter->system_id == widevine_system_id) { - Status status = decryption_key_source_->FetchKeys(iter->data); + ProtectionSystemSpecificInfo info; + RCHECK(info.Parse(iter->raw_box.data(), iter->raw_box.size())); + if (info.system_id() == widevine_system_id) { + Status status = decryption_key_source_->FetchKeys(info.pssh_data()); if (!status.ok()) { LOG(ERROR) << "Error fetching decryption keys: " << status; return false; diff --git a/packager/media/formats/mp4/mp4_muxer.h b/packager/media/formats/mp4/mp4_muxer.h index f9ce6e4895..8f0d2bd4d4 100644 --- a/packager/media/formats/mp4/mp4_muxer.h +++ b/packager/media/formats/mp4/mp4_muxer.h @@ -24,7 +24,6 @@ namespace mp4 { class Segmenter; struct ProtectionSchemeInfo; -struct ProtectionSystemSpecificHeader; struct Track; /// Implements MP4 Muxer for ISO-BMFF. Please refer to ISO/IEC 14496-12: ISO