From 9d67f545ed537234d0084e7859bca62f9d643de5 Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Wed, 10 Feb 2016 09:51:15 -0800 Subject: [PATCH] Add support for v1 PSSH boxes. This moves pssh parsing to its own class in media base. This type can be used to parse and generate pssh boxes. This new type also supports both v0 an v1 boxes. This also modifies the pssh box definition to only have the |raw_box| and moves all pssh parsing and handling into mp4 media parser. A follow-up change will replace the remaining usages of 'raw' pssh box data with the new type. Issue #88 Change-Id: Ic2436ecb5df8b3558b81e600dc095b0efd122ab1 --- packager/media/base/media_base.gyp | 3 + .../base/protection_system_specific_info.cc | 119 +++++++++++++++ .../base/protection_system_specific_info.h | 75 ++++++++++ ...rotection_system_specific_info_unittest.cc | 140 ++++++++++++++++++ packager/media/formats/mp4/box_definitions.cc | 25 +--- packager/media/formats/mp4/box_definitions.h | 2 - .../formats/mp4/box_definitions_comparison.h | 2 +- .../formats/mp4/box_definitions_unittest.cc | 32 +--- .../media/formats/mp4/mp4_media_parser.cc | 7 +- packager/media/formats/mp4/mp4_muxer.h | 1 - 10 files changed, 355 insertions(+), 51 deletions(-) create mode 100644 packager/media/base/protection_system_specific_info.cc create mode 100644 packager/media/base/protection_system_specific_info.h create mode 100644 packager/media/base/protection_system_specific_info_unittest.cc 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