Implement EncryptingFragmenter from Fragmenter
Extract encryption related handlings from Fragmenter and move into new class EncrpytingFragmenter. Change-Id: Id42493cd321fd7e306f8ce522c7ff04990965256
This commit is contained in:
parent
3f0454eced
commit
b25834a910
|
@ -0,0 +1,164 @@
|
||||||
|
// Copyright 2014 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 "media/formats/mp4/encrypting_fragmenter.h"
|
||||||
|
|
||||||
|
#include "media/base/aes_encryptor.h"
|
||||||
|
#include "media/base/buffer_reader.h"
|
||||||
|
#include "media/base/encryption_key_source.h"
|
||||||
|
#include "media/base/media_sample.h"
|
||||||
|
#include "media/formats/mp4/box_definitions.h"
|
||||||
|
#include "media/formats/mp4/cenc.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Generate 64bit IV by default.
|
||||||
|
const size_t kDefaultIvSize = 8u;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace media {
|
||||||
|
namespace mp4 {
|
||||||
|
|
||||||
|
EncryptingFragmenter::EncryptingFragmenter(
|
||||||
|
TrackFragment* traf,
|
||||||
|
bool normalize_presentation_timestamp,
|
||||||
|
scoped_ptr<EncryptionKey> encryption_key,
|
||||||
|
int64 clear_time,
|
||||||
|
uint8 nalu_length_size)
|
||||||
|
: Fragmenter(traf, normalize_presentation_timestamp),
|
||||||
|
encryption_key_(encryption_key.Pass()),
|
||||||
|
nalu_length_size_(nalu_length_size),
|
||||||
|
clear_time_(clear_time) {
|
||||||
|
DCHECK(encryption_key_);
|
||||||
|
}
|
||||||
|
EncryptingFragmenter::~EncryptingFragmenter() {}
|
||||||
|
|
||||||
|
|
||||||
|
Status EncryptingFragmenter::AddSample(scoped_refptr<MediaSample> sample) {
|
||||||
|
DCHECK(sample);
|
||||||
|
if (encryptor_) {
|
||||||
|
Status status = EncryptSample(sample);
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
return Fragmenter::AddSample(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status EncryptingFragmenter::InitializeFragment() {
|
||||||
|
Status status = Fragmenter::InitializeFragment();
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
|
||||||
|
// Enable encryption for this fragment if |clear_time_| becomes non-positive.
|
||||||
|
if (clear_time_ <= 0)
|
||||||
|
return PrepareFragmentForEncryption();
|
||||||
|
|
||||||
|
// Otherwise, this fragment should be in clear text.
|
||||||
|
// At most two sample description entries, an encrypted entry and a clear
|
||||||
|
// entry, are generated. The 1-based clear entry index is always 2.
|
||||||
|
const uint32 kClearSampleDescriptionIndex = 2;
|
||||||
|
|
||||||
|
traf()->header.flags |=
|
||||||
|
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
|
||||||
|
traf()->header.sample_description_index = kClearSampleDescriptionIndex;
|
||||||
|
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncryptingFragmenter::FinalizeFragment() {
|
||||||
|
if (encryptor_) {
|
||||||
|
DCHECK_LE(clear_time_, 0);
|
||||||
|
FinalizeFragmentForEncryption();
|
||||||
|
} else {
|
||||||
|
DCHECK_GT(clear_time_, 0);
|
||||||
|
clear_time_ -= fragment_duration();
|
||||||
|
}
|
||||||
|
Fragmenter::FinalizeFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
Status EncryptingFragmenter::PrepareFragmentForEncryption() {
|
||||||
|
traf()->auxiliary_size.sample_info_sizes.clear();
|
||||||
|
traf()->auxiliary_offset.offsets.clear();
|
||||||
|
return encryptor_ ? Status::OK : CreateEncryptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncryptingFragmenter::FinalizeFragmentForEncryption() {
|
||||||
|
// The offset will be adjusted in Segmenter after knowing moof size.
|
||||||
|
traf()->auxiliary_offset.offsets.push_back(0);
|
||||||
|
|
||||||
|
// Optimize saiz box.
|
||||||
|
SampleAuxiliaryInformationSize& saiz = traf()->auxiliary_size;
|
||||||
|
saiz.sample_count = traf()->runs[0].sample_sizes.size();
|
||||||
|
if (!saiz.sample_info_sizes.empty()) {
|
||||||
|
if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
|
||||||
|
&saiz.default_sample_info_size)) {
|
||||||
|
saiz.default_sample_info_size = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// |sample_info_sizes| table is filled in only for subsample encryption,
|
||||||
|
// otherwise |sample_info_size| is just the IV size.
|
||||||
|
DCHECK(!IsSubsampleEncryptionRequired());
|
||||||
|
saiz.default_sample_info_size = encryptor_->iv().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Status EncryptingFragmenter::CreateEncryptor() {
|
||||||
|
DCHECK(encryption_key_);
|
||||||
|
|
||||||
|
scoped_ptr<AesCtrEncryptor> encryptor(new AesCtrEncryptor());
|
||||||
|
const bool initialized = encryption_key_->iv.empty()
|
||||||
|
? encryptor->InitializeWithRandomIv(
|
||||||
|
encryption_key_->key, kDefaultIvSize)
|
||||||
|
: encryptor->InitializeWithIv(
|
||||||
|
encryption_key_->key, encryption_key_->iv);
|
||||||
|
if (!initialized)
|
||||||
|
return Status(error::MUXER_FAILURE, "Failed to create the encryptor.");
|
||||||
|
encryptor_ = encryptor.Pass();
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncryptingFragmenter::EncryptBytes(uint8* data, uint32 size) {
|
||||||
|
DCHECK(encryptor_);
|
||||||
|
CHECK(encryptor_->Encrypt(data, size, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
|
||||||
|
DCHECK(encryptor_);
|
||||||
|
|
||||||
|
FrameCENCInfo cenc_info(encryptor_->iv());
|
||||||
|
uint8* data = sample->writable_data();
|
||||||
|
if (!IsSubsampleEncryptionRequired()) {
|
||||||
|
EncryptBytes(data, sample->data_size());
|
||||||
|
} else {
|
||||||
|
BufferReader reader(data, sample->data_size());
|
||||||
|
while (reader.HasBytes(1)) {
|
||||||
|
uint64 nalu_length;
|
||||||
|
if (!reader.ReadNBytesInto8(&nalu_length, nalu_length_size_))
|
||||||
|
return Status(error::MUXER_FAILURE, "Fail to read nalu_length.");
|
||||||
|
|
||||||
|
SubsampleEntry subsample;
|
||||||
|
subsample.clear_bytes = nalu_length_size_ + 1;
|
||||||
|
subsample.cypher_bytes = nalu_length - 1;
|
||||||
|
if (!reader.SkipBytes(nalu_length)) {
|
||||||
|
return Status(error::MUXER_FAILURE,
|
||||||
|
"Sample size does not match nalu_length.");
|
||||||
|
}
|
||||||
|
|
||||||
|
EncryptBytes(data + subsample.clear_bytes, subsample.cypher_bytes);
|
||||||
|
cenc_info.AddSubsample(subsample);
|
||||||
|
data += nalu_length_size_ + nalu_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The length of per-sample auxiliary datum, defined in CENC ch. 7.
|
||||||
|
traf()->auxiliary_size.sample_info_sizes.push_back(cenc_info.ComputeSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
cenc_info.Write(aux_data());
|
||||||
|
encryptor_->UpdateIv();
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mp4
|
||||||
|
} // namespace media
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2014 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_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
|
||||||
|
#define MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
|
||||||
|
|
||||||
|
#include "media/formats/mp4/fragmenter.h"
|
||||||
|
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
class AesCtrEncryptor;
|
||||||
|
struct EncryptionKey;
|
||||||
|
|
||||||
|
namespace mp4 {
|
||||||
|
|
||||||
|
/// EncryptingFragmenter generates MP4 fragments with sample encrypted.
|
||||||
|
class EncryptingFragmenter : public Fragmenter {
|
||||||
|
public:
|
||||||
|
/// @param traf points to a TrackFragment box.
|
||||||
|
/// @param normalize_presentation_timestamp defines whether PTS should be
|
||||||
|
/// normalized to start from zero.
|
||||||
|
/// @param encryption_key contains the encryption parameters.
|
||||||
|
/// @param clear_time specifies clear lead duration in units of the current
|
||||||
|
/// track's timescale.
|
||||||
|
/// @param nalu_length_size specifies the size of NAL unit length, in bytes,
|
||||||
|
/// for subsample encryption.
|
||||||
|
EncryptingFragmenter(TrackFragment* traf,
|
||||||
|
bool normalize_presentation_timestamp,
|
||||||
|
scoped_ptr<EncryptionKey> encryption_key,
|
||||||
|
int64 clear_time,
|
||||||
|
uint8 nalu_length_size);
|
||||||
|
|
||||||
|
virtual ~EncryptingFragmenter();
|
||||||
|
|
||||||
|
/// @name Fragmenter implementation overrides.
|
||||||
|
/// @{
|
||||||
|
virtual Status AddSample(scoped_refptr<MediaSample> sample) OVERRIDE;
|
||||||
|
virtual Status InitializeFragment() OVERRIDE;
|
||||||
|
virtual void FinalizeFragment() OVERRIDE;
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Prepare current fragment for encryption.
|
||||||
|
/// @return OK on success, an error status otherwise.
|
||||||
|
virtual Status PrepareFragmentForEncryption();
|
||||||
|
/// Finalize current fragment for encryption.
|
||||||
|
virtual void FinalizeFragmentForEncryption();
|
||||||
|
|
||||||
|
/// Create the encryptor for the internal encryption key. The existing
|
||||||
|
/// encryptor will be reset if it is not NULL.
|
||||||
|
/// @return OK on success, an error status otherwise.
|
||||||
|
Status CreateEncryptor();
|
||||||
|
|
||||||
|
EncryptionKey* encryption_key() { return encryption_key_.get(); }
|
||||||
|
AesCtrEncryptor* encryptor() { return encryptor_.get(); }
|
||||||
|
|
||||||
|
void set_encryption_key(scoped_ptr<EncryptionKey> encryption_key) {
|
||||||
|
encryption_key_ = encryption_key.Pass();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void EncryptBytes(uint8* data, uint32 size);
|
||||||
|
Status EncryptSample(scoped_refptr<MediaSample> sample);
|
||||||
|
|
||||||
|
// Should we enable subsample encryption?
|
||||||
|
bool IsSubsampleEncryptionRequired() { return nalu_length_size_ != 0; }
|
||||||
|
|
||||||
|
scoped_ptr<EncryptionKey> encryption_key_;
|
||||||
|
scoped_ptr<AesCtrEncryptor> encryptor_;
|
||||||
|
// If this stream contains AVC, subsample encryption specifies that the size
|
||||||
|
// and type of NAL units remain unencrypted. This field specifies the size of
|
||||||
|
// the size field. Can be 1, 2 or 4 bytes.
|
||||||
|
const uint8 nalu_length_size_;
|
||||||
|
int64 clear_time_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mp4
|
||||||
|
} // namespace media
|
||||||
|
|
||||||
|
#endif // MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
|
|
@ -6,50 +6,20 @@
|
||||||
|
|
||||||
#include "media/formats/mp4/fragmenter.h"
|
#include "media/formats/mp4/fragmenter.h"
|
||||||
|
|
||||||
#include "media/base/aes_encryptor.h"
|
|
||||||
#include "media/base/buffer_reader.h"
|
|
||||||
#include "media/base/buffer_writer.h"
|
#include "media/base/buffer_writer.h"
|
||||||
#include "media/base/encryption_key_source.h"
|
|
||||||
#include "media/base/media_sample.h"
|
#include "media/base/media_sample.h"
|
||||||
#include "media/formats/mp4/box_definitions.h"
|
#include "media/formats/mp4/box_definitions.h"
|
||||||
#include "media/formats/mp4/cenc.h"
|
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Generate 64bit IV by default.
|
|
||||||
const size_t kDefaultIvSize = 8u;
|
|
||||||
const int64 kInvalidTime = kint64max;
|
const int64 kInvalidTime = kint64max;
|
||||||
|
|
||||||
// Optimize sample entries table. If all values in |entries| are identical,
|
|
||||||
// then |entries| is cleared and the value is assigned to |default_value|;
|
|
||||||
// otherwise it is a NOP. Return true if the table is optimized.
|
|
||||||
template <typename T>
|
|
||||||
bool OptimizeSampleEntries(std::vector<T>* entries, T* default_value) {
|
|
||||||
DCHECK(entries);
|
|
||||||
DCHECK(default_value);
|
|
||||||
DCHECK(!entries->empty());
|
|
||||||
|
|
||||||
typename std::vector<T>::const_iterator it = entries->begin();
|
|
||||||
T value = *it;
|
|
||||||
for (; it < entries->end(); ++it)
|
|
||||||
if (value != *it)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Clear |entries| if it contains only one value.
|
|
||||||
entries->clear();
|
|
||||||
*default_value = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Fragmenter::Fragmenter(TrackFragment* traf,
|
Fragmenter::Fragmenter(TrackFragment* traf,
|
||||||
bool normalize_presentation_timestamp)
|
bool normalize_presentation_timestamp)
|
||||||
: traf_(traf),
|
: traf_(traf),
|
||||||
nalu_length_size_(0),
|
|
||||||
clear_time_(0),
|
|
||||||
fragment_finalized_(false),
|
fragment_finalized_(false),
|
||||||
fragment_duration_(0),
|
fragment_duration_(0),
|
||||||
normalize_presentation_timestamp_(normalize_presentation_timestamp),
|
normalize_presentation_timestamp_(normalize_presentation_timestamp),
|
||||||
|
@ -59,47 +29,21 @@ Fragmenter::Fragmenter(TrackFragment* traf,
|
||||||
DCHECK(traf);
|
DCHECK(traf);
|
||||||
}
|
}
|
||||||
|
|
||||||
Fragmenter::Fragmenter(TrackFragment* traf,
|
|
||||||
bool normalize_presentation_timestamp,
|
|
||||||
scoped_ptr<EncryptionKey> encryption_key,
|
|
||||||
int64 clear_time,
|
|
||||||
uint8 nalu_length_size)
|
|
||||||
: traf_(traf),
|
|
||||||
encryption_key_(encryption_key.Pass()),
|
|
||||||
nalu_length_size_(nalu_length_size),
|
|
||||||
clear_time_(clear_time),
|
|
||||||
fragment_finalized_(false),
|
|
||||||
fragment_duration_(0),
|
|
||||||
normalize_presentation_timestamp_(normalize_presentation_timestamp),
|
|
||||||
presentation_start_time_(kInvalidTime),
|
|
||||||
earliest_presentation_time_(kInvalidTime),
|
|
||||||
first_sap_time_(kInvalidTime) {
|
|
||||||
DCHECK(traf);
|
|
||||||
DCHECK(encryption_key_);
|
|
||||||
}
|
|
||||||
|
|
||||||
Fragmenter::~Fragmenter() {}
|
Fragmenter::~Fragmenter() {}
|
||||||
|
|
||||||
Status Fragmenter::AddSample(scoped_refptr<MediaSample> sample) {
|
Status Fragmenter::AddSample(scoped_refptr<MediaSample> sample) {
|
||||||
|
DCHECK(sample);
|
||||||
CHECK_GT(sample->duration(), 0);
|
CHECK_GT(sample->duration(), 0);
|
||||||
|
|
||||||
if (encryptor_) {
|
|
||||||
Status status = EncryptSample(sample);
|
|
||||||
if (!status.ok())
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill in sample parameters. It will be optimized later.
|
// Fill in sample parameters. It will be optimized later.
|
||||||
traf_->runs[0].sample_sizes.push_back(sample->data_size());
|
traf_->runs[0].sample_sizes.push_back(sample->data_size());
|
||||||
traf_->runs[0].sample_durations.push_back(sample->duration());
|
traf_->runs[0].sample_durations.push_back(sample->duration());
|
||||||
traf_->runs[0].sample_flags.push_back(
|
traf_->runs[0].sample_flags.push_back(
|
||||||
sample->is_key_frame() ? 0 : TrackFragmentHeader::kNonKeySampleMask);
|
sample->is_key_frame() ? 0 : TrackFragmentHeader::kNonKeySampleMask);
|
||||||
traf_->runs[0]
|
traf_->runs[0].sample_composition_time_offsets.push_back(sample->pts() -
|
||||||
.sample_composition_time_offsets.push_back(sample->pts() - sample->dts());
|
sample->dts());
|
||||||
if (sample->pts() != sample->dts()) {
|
if (sample->pts() != sample->dts())
|
||||||
traf_->runs[0].flags |=
|
traf_->runs[0].flags |= TrackFragmentRun::kSampleCompTimeOffsetsPresentMask;
|
||||||
TrackFragmentRun::kSampleCompTimeOffsetsPresentMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
data_->AppendArray(sample->data(), sample->data_size());
|
data_->AppendArray(sample->data(), sample->data_size());
|
||||||
fragment_duration_ += sample->duration();
|
fragment_duration_ += sample->duration();
|
||||||
|
@ -114,7 +58,7 @@ Status Fragmenter::AddSample(scoped_refptr<MediaSample> sample) {
|
||||||
presentation_start_time_ = pts;
|
presentation_start_time_ = pts;
|
||||||
pts = 0;
|
pts = 0;
|
||||||
} else {
|
} else {
|
||||||
// Can we safely assume the first sample in the media has the earliest
|
// Is it safe to assume the first sample in the media has the earliest
|
||||||
// presentation timestamp?
|
// presentation timestamp?
|
||||||
DCHECK_GE(pts, presentation_start_time_);
|
DCHECK_GE(pts, presentation_start_time_);
|
||||||
pts -= presentation_start_time_;
|
pts -= presentation_start_time_;
|
||||||
|
@ -145,30 +89,10 @@ Status Fragmenter::InitializeFragment() {
|
||||||
first_sap_time_ = kInvalidTime;
|
first_sap_time_ = kInvalidTime;
|
||||||
data_.reset(new BufferWriter());
|
data_.reset(new BufferWriter());
|
||||||
aux_data_.reset(new BufferWriter());
|
aux_data_.reset(new BufferWriter());
|
||||||
|
|
||||||
if (!encryption_key_)
|
|
||||||
return Status::OK;
|
|
||||||
|
|
||||||
// Enable encryption for this fragment if decode time passes clear lead.
|
|
||||||
if (static_cast<int64>(traf_->decode_time.decode_time) >= clear_time_)
|
|
||||||
return PrepareFragmentForEncryption();
|
|
||||||
|
|
||||||
// Otherwise, this fragment should be in clear text.
|
|
||||||
// We generate at most two sample description entries, encrypted entry and
|
|
||||||
// clear entry. The 1-based clear entry index is always 2.
|
|
||||||
const uint32 kClearSampleDescriptionIndex = 2;
|
|
||||||
|
|
||||||
traf_->header.flags |=
|
|
||||||
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
|
|
||||||
traf_->header.sample_description_index = kClearSampleDescriptionIndex;
|
|
||||||
|
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fragmenter::FinalizeFragment() {
|
void Fragmenter::FinalizeFragment() {
|
||||||
if (encryptor_)
|
|
||||||
FinalizeFragmentForEncryption();
|
|
||||||
|
|
||||||
// Optimize trun box.
|
// Optimize trun box.
|
||||||
traf_->runs[0].sample_count = traf_->runs[0].sample_sizes.size();
|
traf_->runs[0].sample_count = traf_->runs[0].sample_sizes.size();
|
||||||
if (OptimizeSampleEntries(&traf_->runs[0].sample_durations,
|
if (OptimizeSampleEntries(&traf_->runs[0].sample_durations,
|
||||||
|
@ -209,88 +133,6 @@ void Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
|
||||||
reference->earliest_presentation_time = earliest_presentation_time_;
|
reference->earliest_presentation_time = earliest_presentation_time_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Fragmenter::PrepareFragmentForEncryption() {
|
|
||||||
traf_->auxiliary_size.sample_info_sizes.clear();
|
|
||||||
traf_->auxiliary_offset.offsets.clear();
|
|
||||||
return encryptor_ ? Status::OK : CreateEncryptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Fragmenter::FinalizeFragmentForEncryption() {
|
|
||||||
// The offset will be adjusted in Segmenter when we know moof size.
|
|
||||||
traf_->auxiliary_offset.offsets.push_back(0);
|
|
||||||
|
|
||||||
// Optimize saiz box.
|
|
||||||
SampleAuxiliaryInformationSize& saiz = traf_->auxiliary_size;
|
|
||||||
saiz.sample_count = traf_->runs[0].sample_sizes.size();
|
|
||||||
if (!saiz.sample_info_sizes.empty()) {
|
|
||||||
if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
|
|
||||||
&saiz.default_sample_info_size)) {
|
|
||||||
saiz.default_sample_info_size = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// |sample_info_sizes| table is filled in only for subsample encryption,
|
|
||||||
// otherwise |sample_info_size| is just the IV size.
|
|
||||||
DCHECK(!IsSubsampleEncryptionRequired());
|
|
||||||
saiz.default_sample_info_size = encryptor_->iv().size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Status Fragmenter::CreateEncryptor() {
|
|
||||||
DCHECK(encryption_key_);
|
|
||||||
|
|
||||||
scoped_ptr<AesCtrEncryptor> encryptor(new AesCtrEncryptor());
|
|
||||||
const bool initialized = encryption_key_->iv.empty()
|
|
||||||
? encryptor->InitializeWithRandomIv(
|
|
||||||
encryption_key_->key, kDefaultIvSize)
|
|
||||||
: encryptor->InitializeWithIv(
|
|
||||||
encryption_key_->key, encryption_key_->iv);
|
|
||||||
if (!initialized)
|
|
||||||
return Status(error::MUXER_FAILURE, "Failed to create the encryptor.");
|
|
||||||
encryptor_ = encryptor.Pass();
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Fragmenter::EncryptBytes(uint8* data, uint32 size) {
|
|
||||||
DCHECK(encryptor_);
|
|
||||||
CHECK(encryptor_->Encrypt(data, size, data));
|
|
||||||
}
|
|
||||||
|
|
||||||
Status Fragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
|
|
||||||
DCHECK(encryptor_);
|
|
||||||
|
|
||||||
FrameCENCInfo cenc_info(encryptor_->iv());
|
|
||||||
uint8* data = sample->writable_data();
|
|
||||||
if (!IsSubsampleEncryptionRequired()) {
|
|
||||||
EncryptBytes(data, sample->data_size());
|
|
||||||
} else {
|
|
||||||
BufferReader reader(data, sample->data_size());
|
|
||||||
while (reader.HasBytes(1)) {
|
|
||||||
uint64 nalu_length;
|
|
||||||
if (!reader.ReadNBytesInto8(&nalu_length, nalu_length_size_))
|
|
||||||
return Status(error::MUXER_FAILURE, "Fail to read nalu_length.");
|
|
||||||
|
|
||||||
SubsampleEntry subsample;
|
|
||||||
subsample.clear_bytes = nalu_length_size_ + 1;
|
|
||||||
subsample.cypher_bytes = nalu_length - 1;
|
|
||||||
if (!reader.SkipBytes(nalu_length)) {
|
|
||||||
return Status(error::MUXER_FAILURE,
|
|
||||||
"Sample size does not match nalu_length.");
|
|
||||||
}
|
|
||||||
|
|
||||||
EncryptBytes(data + subsample.clear_bytes, subsample.cypher_bytes);
|
|
||||||
cenc_info.AddSubsample(subsample);
|
|
||||||
data += nalu_length_size_ + nalu_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The length of per-sample auxiliary datum, defined in CENC ch. 7.
|
|
||||||
traf_->auxiliary_size.sample_info_sizes.push_back(cenc_info.ComputeSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
cenc_info.Write(aux_data_.get());
|
|
||||||
encryptor_->UpdateIv();
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Fragmenter::StartsWithSAP() {
|
bool Fragmenter::StartsWithSAP() {
|
||||||
DCHECK(!traf_->runs.empty());
|
DCHECK(!traf_->runs.empty());
|
||||||
uint32 start_sample_flag;
|
uint32 start_sample_flag;
|
||||||
|
|
|
@ -15,54 +15,36 @@
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
class AesCtrEncryptor;
|
|
||||||
class BufferWriter;
|
class BufferWriter;
|
||||||
class MediaSample;
|
class MediaSample;
|
||||||
|
|
||||||
struct EncryptionKey;
|
|
||||||
|
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
struct MovieFragment;
|
|
||||||
struct SegmentReference;
|
struct SegmentReference;
|
||||||
struct TrackFragment;
|
struct TrackFragment;
|
||||||
|
|
||||||
/// Fragmenter is responsible for the generation of MP4 fragments, i.e. traf
|
/// Fragmenter is responsible for the generation of MP4 fragments, i.e. 'traf'
|
||||||
/// box and corresponding mdat box. The samples are also encrypted if encryption
|
/// box and corresponding 'mdat' box.
|
||||||
/// is requested.
|
|
||||||
class Fragmenter {
|
class Fragmenter {
|
||||||
public:
|
public:
|
||||||
/// @param traf points to a TrackFragment box.
|
/// @param traf points to a TrackFragment box.
|
||||||
/// @param normalize_presentation_timestamp defines whether PTS should be
|
/// @param normalize_presentation_timestamp defines whether PTS should be
|
||||||
/// normalized to start from zero.
|
/// normalized to start from zero.
|
||||||
Fragmenter(TrackFragment* traf,
|
Fragmenter(TrackFragment* traf, bool normalize_presentation_timestamp);
|
||||||
bool normalize_presentation_timestamp);
|
|
||||||
|
|
||||||
/// @param traf points to a TrackFragment box.
|
|
||||||
/// @param normalize_presentation_timestamp defines whether PTS should be
|
|
||||||
/// normalized to start from zero.
|
|
||||||
/// @param encryption_key contains the encryption parameters.
|
|
||||||
/// @param clear_time specifies clear lead duration in units of the current
|
|
||||||
/// track's timescale.
|
|
||||||
/// @param nalu_length_size NAL unit length size, in bytes, for subsample
|
|
||||||
/// encryption.
|
|
||||||
Fragmenter(TrackFragment* traf,
|
|
||||||
bool normalize_presentation_timestamp,
|
|
||||||
scoped_ptr<EncryptionKey> encryption_key,
|
|
||||||
int64 clear_time,
|
|
||||||
uint8 nalu_length_size);
|
|
||||||
|
|
||||||
virtual ~Fragmenter();
|
virtual ~Fragmenter();
|
||||||
|
|
||||||
/// Add a sample to the fragmenter.
|
/// Add a sample to the fragmenter.
|
||||||
Status AddSample(scoped_refptr<MediaSample> sample);
|
/// @param sample points to the sample to be added.
|
||||||
|
/// @return OK on success, an error status otherwise.
|
||||||
|
virtual Status AddSample(scoped_refptr<MediaSample> sample);
|
||||||
|
|
||||||
/// Initialize the fragment with default data.
|
/// Initialize the fragment with default data.
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @return OK on success, an error status otherwise.
|
||||||
Status InitializeFragment();
|
virtual Status InitializeFragment();
|
||||||
|
|
||||||
/// Finalize and optimize the fragment.
|
/// Finalize and optimize the fragment.
|
||||||
void FinalizeFragment();
|
virtual void FinalizeFragment();
|
||||||
|
|
||||||
/// Fill @a reference with current fragment information.
|
/// Fill @a reference with current fragment information.
|
||||||
void GenerateSegmentReference(SegmentReference* reference);
|
void GenerateSegmentReference(SegmentReference* reference);
|
||||||
|
@ -77,45 +59,19 @@ class Fragmenter {
|
||||||
BufferWriter* aux_data() { return aux_data_.get(); }
|
BufferWriter* aux_data() { return aux_data_.get(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Prepare current fragment for encryption.
|
|
||||||
/// @return OK on success, an error status otherwise.
|
|
||||||
virtual Status PrepareFragmentForEncryption();
|
|
||||||
/// Finalize current fragment for encryption.
|
|
||||||
virtual void FinalizeFragmentForEncryption();
|
|
||||||
|
|
||||||
/// Create the encryptor for the internal encryption key. The existing
|
|
||||||
/// encryptor will be reset if it is not NULL.
|
|
||||||
/// @return OK on success, an error status otherwise.
|
|
||||||
Status CreateEncryptor();
|
|
||||||
|
|
||||||
TrackFragment* traf() { return traf_; }
|
TrackFragment* traf() { return traf_; }
|
||||||
EncryptionKey* encryption_key() { return encryption_key_.get(); }
|
|
||||||
AesCtrEncryptor* encryptor() { return encryptor_.get(); }
|
|
||||||
|
|
||||||
void set_encryption_key(scoped_ptr<EncryptionKey> encryption_key) {
|
/// Optimize sample entries table. If all values in @a entries are identical,
|
||||||
encryption_key_ = encryption_key.Pass();
|
/// then @a entries is cleared and the value is assigned to @a default_value;
|
||||||
}
|
/// otherwise it is a NOP. Return true if the table is optimized.
|
||||||
|
template <typename T>
|
||||||
|
bool OptimizeSampleEntries(std::vector<T>* entries, T* default_value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void EncryptBytes(uint8* data, uint32 size);
|
|
||||||
Status EncryptSample(scoped_refptr<MediaSample> sample);
|
|
||||||
|
|
||||||
// Should we enable subsample encryption?
|
|
||||||
bool IsSubsampleEncryptionRequired() { return nalu_length_size_ != 0; }
|
|
||||||
|
|
||||||
// Check if the current fragment starts with SAP.
|
// Check if the current fragment starts with SAP.
|
||||||
bool StartsWithSAP();
|
bool StartsWithSAP();
|
||||||
|
|
||||||
TrackFragment* traf_;
|
TrackFragment* traf_;
|
||||||
|
|
||||||
scoped_ptr<EncryptionKey> encryption_key_;
|
|
||||||
scoped_ptr<AesCtrEncryptor> encryptor_;
|
|
||||||
// If this stream contains AVC, subsample encryption specifies that the size
|
|
||||||
// and type of NAL units remain unencrypted. This field specifies the size of
|
|
||||||
// the size field. Can be 1, 2 or 4 bytes.
|
|
||||||
const uint8 nalu_length_size_;
|
|
||||||
const int64 clear_time_;
|
|
||||||
|
|
||||||
bool fragment_finalized_;
|
bool fragment_finalized_;
|
||||||
uint64 fragment_duration_;
|
uint64 fragment_duration_;
|
||||||
bool normalize_presentation_timestamp_;
|
bool normalize_presentation_timestamp_;
|
||||||
|
@ -128,6 +84,25 @@ class Fragmenter {
|
||||||
DISALLOW_COPY_AND_ASSIGN(Fragmenter);
|
DISALLOW_COPY_AND_ASSIGN(Fragmenter);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool Fragmenter::OptimizeSampleEntries(std::vector<T>* entries,
|
||||||
|
T* default_value) {
|
||||||
|
DCHECK(entries);
|
||||||
|
DCHECK(default_value);
|
||||||
|
DCHECK(!entries->empty());
|
||||||
|
|
||||||
|
typename std::vector<T>::const_iterator it = entries->begin();
|
||||||
|
T value = *it;
|
||||||
|
for (; it < entries->end(); ++it)
|
||||||
|
if (value != *it)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Clear |entries| if it contains only one value.
|
||||||
|
entries->clear();
|
||||||
|
*default_value = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mp4
|
} // namespace mp4
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ KeyRotationFragmenter::KeyRotationFragmenter(
|
||||||
int64 crypto_period_duration,
|
int64 crypto_period_duration,
|
||||||
int64 clear_time,
|
int64 clear_time,
|
||||||
uint8 nalu_length_size)
|
uint8 nalu_length_size)
|
||||||
: Fragmenter(traf,
|
: EncryptingFragmenter(traf,
|
||||||
normalize_presentation_timestamp,
|
normalize_presentation_timestamp,
|
||||||
scoped_ptr<EncryptionKey>(new EncryptionKey()),
|
scoped_ptr<EncryptionKey>(new EncryptionKey()),
|
||||||
clear_time,
|
clear_time,
|
||||||
|
@ -57,29 +57,29 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption() {
|
||||||
prev_crypto_period_index_ = current_crypto_period_index;
|
prev_crypto_period_index_ = current_crypto_period_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryptionKey* encryption_key = Fragmenter::encryption_key();
|
EncryptionKey* encryption_key = EncryptingFragmenter::encryption_key();
|
||||||
DCHECK(encryption_key);
|
DCHECK(encryption_key);
|
||||||
AesCtrEncryptor* encryptor = Fragmenter::encryptor();
|
AesCtrEncryptor* encryptor = EncryptingFragmenter::encryptor();
|
||||||
DCHECK(encryptor);
|
DCHECK(encryptor);
|
||||||
|
|
||||||
// We support key rotation in fragment boundary only, i.e. there is at most
|
// Key rotation happens in fragment boundary only in this implementation,
|
||||||
// one key for a single fragment. So we should have only one entry in
|
// i.e. there is at most one key for the fragment. So there should be only
|
||||||
// Sample Group Description box and one entry in Sample to Group box.
|
// one entry in SampleGroupDescription box and one entry in SampleToGroup box.
|
||||||
// Fill in Sample Group Description box information.
|
// Fill in SampleGroupDescription box information.
|
||||||
traf()->sample_group_description.grouping_type = FOURCC_SEIG;
|
traf()->sample_group_description.grouping_type = FOURCC_SEIG;
|
||||||
traf()->sample_group_description.entries.resize(1);
|
traf()->sample_group_description.entries.resize(1);
|
||||||
traf()->sample_group_description.entries[0].is_encrypted = true;
|
traf()->sample_group_description.entries[0].is_encrypted = true;
|
||||||
traf()->sample_group_description.entries[0].iv_size = encryptor->iv().size();
|
traf()->sample_group_description.entries[0].iv_size = encryptor->iv().size();
|
||||||
traf()->sample_group_description.entries[0].key_id = encryption_key->key_id;
|
traf()->sample_group_description.entries[0].key_id = encryption_key->key_id;
|
||||||
|
|
||||||
// Fill in Sample to Group box information.
|
// Fill in SampleToGroup box information.
|
||||||
traf()->sample_to_group.grouping_type = FOURCC_SEIG;
|
traf()->sample_to_group.grouping_type = FOURCC_SEIG;
|
||||||
traf()->sample_to_group.entries.resize(1);
|
traf()->sample_to_group.entries.resize(1);
|
||||||
// sample_count is adjusted in |FinalizeFragment| later.
|
// sample_count is adjusted in |FinalizeFragment| later.
|
||||||
traf()->sample_to_group.entries[0].group_description_index =
|
traf()->sample_to_group.entries[0].group_description_index =
|
||||||
SampleToGroupEntry::kTrackFragmentGroupDescriptionIndexBase + 1;
|
SampleToGroupEntry::kTrackFragmentGroupDescriptionIndexBase + 1;
|
||||||
|
|
||||||
// We need one and only one pssh box.
|
// One and only one 'pssh' box is needed.
|
||||||
if (moof_->pssh.empty())
|
if (moof_->pssh.empty())
|
||||||
moof_->pssh.resize(1);
|
moof_->pssh.resize(1);
|
||||||
moof_->pssh[0].raw_box = encryption_key->pssh;
|
moof_->pssh[0].raw_box = encryption_key->pssh;
|
||||||
|
@ -88,7 +88,7 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyRotationFragmenter::FinalizeFragmentForEncryption() {
|
void KeyRotationFragmenter::FinalizeFragmentForEncryption() {
|
||||||
Fragmenter::FinalizeFragmentForEncryption();
|
EncryptingFragmenter::FinalizeFragmentForEncryption();
|
||||||
DCHECK_EQ(1u, traf()->sample_to_group.entries.size());
|
DCHECK_EQ(1u, traf()->sample_to_group.entries.size());
|
||||||
traf()->sample_to_group.entries[0].sample_count =
|
traf()->sample_to_group.entries[0].sample_count =
|
||||||
traf()->auxiliary_size.sample_count;
|
traf()->auxiliary_size.sample_count;
|
||||||
|
@ -96,5 +96,3 @@ void KeyRotationFragmenter::FinalizeFragmentForEncryption() {
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace mp4
|
} // namespace mp4
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,16 @@
|
||||||
#define MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_
|
#define MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_
|
||||||
|
|
||||||
#include "media/base/encryption_key_source.h"
|
#include "media/base/encryption_key_source.h"
|
||||||
#include "media/formats/mp4/fragmenter.h"
|
#include "media/formats/mp4/encrypting_fragmenter.h"
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
class KeyRotationFragmenter : public Fragmenter {
|
struct MovieFragment;
|
||||||
|
|
||||||
|
/// KeyRotationFragmenter generates MP4 fragments with sample encrypted by
|
||||||
|
/// rotation keys.
|
||||||
|
class KeyRotationFragmenter : public EncryptingFragmenter {
|
||||||
public:
|
public:
|
||||||
/// @param moof points to a MovieFragment box.
|
/// @param moof points to a MovieFragment box.
|
||||||
/// @param traf points to a TrackFragment box.
|
/// @param traf points to a TrackFragment box.
|
||||||
|
|
|
@ -37,6 +37,8 @@
|
||||||
'composition_offset_iterator.h',
|
'composition_offset_iterator.h',
|
||||||
'decoding_time_iterator.cc',
|
'decoding_time_iterator.cc',
|
||||||
'decoding_time_iterator.h',
|
'decoding_time_iterator.h',
|
||||||
|
'encrypting_fragmenter.cc',
|
||||||
|
'encrypting_fragmenter.h',
|
||||||
'es_descriptor.cc',
|
'es_descriptor.cc',
|
||||||
'es_descriptor.h',
|
'es_descriptor.h',
|
||||||
'fourccs.h',
|
'fourccs.h',
|
||||||
|
|
|
@ -180,14 +180,14 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
||||||
GenerateEncryptedSampleEntry(
|
GenerateEncryptedSampleEntry(
|
||||||
*encryption_key, clear_lead_in_seconds, &description);
|
*encryption_key, clear_lead_in_seconds, &description);
|
||||||
|
|
||||||
// We need one and only one pssh box.
|
// One and only one pssh box is needed.
|
||||||
if (moov_->pssh.empty()) {
|
if (moov_->pssh.empty()) {
|
||||||
moov_->pssh.resize(1);
|
moov_->pssh.resize(1);
|
||||||
moov_->pssh[0].raw_box = encryption_key->pssh;
|
moov_->pssh[0].raw_box = encryption_key->pssh;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragmenters_[i] =
|
fragmenters_[i] = new EncryptingFragmenter(
|
||||||
new Fragmenter(&moof_->tracks[i],
|
&moof_->tracks[i],
|
||||||
options_.normalize_presentation_timestamp,
|
options_.normalize_presentation_timestamp,
|
||||||
encryption_key.Pass(),
|
encryption_key.Pass(),
|
||||||
clear_lead_in_seconds * streams[i]->info()->time_scale(),
|
clear_lead_in_seconds * streams[i]->info()->time_scale(),
|
||||||
|
|
Loading…
Reference in New Issue