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
This commit is contained in:
Jacob Trimble 2016-02-10 09:51:15 -08:00
parent 0ca1160474
commit 9d67f545ed
10 changed files with 355 additions and 51 deletions

View File

@ -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',

View File

@ -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<ProtectionSystemSpecificInfo>* 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<uint8_t> 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<uint32_t>(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<uint8_t>(writer.Buffer(), writer.Buffer() + writer.Size());
}
} // namespace media
} // namespace edash_packager

View File

@ -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 <stdint.h>
#include <vector>
#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<ProtectionSystemSpecificInfo>* 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<uint8_t> CreateBox() const;
uint8_t version() const { return version_; }
const std::vector<uint8_t>& system_id() const { return system_id_; }
const std::vector<std::vector<uint8_t>>& key_ids() const { return key_ids_; }
const std::vector<uint8_t>& 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<uint8_t>& system_id) {
DCHECK_EQ(16u, system_id.size());
system_id_ = system_id;
}
void add_key_id(const std::vector<uint8_t>& 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<uint8_t>& pssh_data) {
pssh_data_ = pssh_data;
}
private:
uint8_t version_;
std::vector<uint8_t> system_id_;
std::vector<std::vector<uint8_t>> key_ids_;
std::vector<uint8_t> 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_

View File

@ -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 <gtest/gtest.h>
#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<uint8_t> v0_box_;
const std::vector<uint8_t> v1_box_;
const std::vector<uint8_t> test_system_id_;
const std::vector<uint8_t> test_key_id_;
const std::vector<uint8_t> test_pssh_data_;
};
TEST_F(PsshTest, ParseBoxes_SupportsV0) {
std::vector<ProtectionSystemSpecificInfo> 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<ProtectionSystemSpecificInfo> 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<uint8_t> 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<ProtectionSystemSpecificInfo> 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

View File

@ -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();
}
}
SampleAuxiliaryInformationOffset::SampleAuxiliaryInformationOffset() {}

View File

@ -57,8 +57,6 @@ struct SegmentType : FileType {
struct ProtectionSystemSpecificHeader : FullBox {
DECLARE_BOX_METHODS(ProtectionSystemSpecificHeader);
std::vector<uint8_t> system_id;
std::vector<uint8_t> data;
std::vector<uint8_t> raw_box;
};

View File

@ -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,

View File

@ -9,6 +9,7 @@
#include <limits>
#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;

View File

@ -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<ProtectionSystemSpecificHeader>::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;

View File

@ -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