Skip to content

Commit

Permalink
Only encrypt video slice NAL units.
Browse files Browse the repository at this point in the history
For non-video slices, the data is not encrypted.  This also skips the
frame headers for H.264; support for H.265 will be added later.

Issue #40

Change-Id: Id0cb0fb9ddb6adedf63ef4aef6b3a26260a21654
  • Loading branch information
TheModMaker committed Feb 22, 2016
1 parent 74951e3 commit 96abd90
Show file tree
Hide file tree
Showing 15 changed files with 402 additions and 72 deletions.
Binary file modified packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4
Binary file not shown.
Binary file modified packager/app/test/testdata/bear-640x360-v-live-cenc-golden-2.m4s
Binary file not shown.
Binary file modified packager/app/test/testdata/bear-640x360-v-live-cenc-golden-3.m4s
Binary file not shown.
Binary file not shown.
Binary file not shown.
5 changes: 5 additions & 0 deletions packager/media/base/decrypt_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ namespace media {
/// result, and then copying each byte from the decrypted block over the
/// corresponding encrypted byte.
struct SubsampleEntry {
SubsampleEntry()
: clear_bytes(0), cipher_bytes(0) {}
SubsampleEntry(uint16_t clear_bytes, uint32_t cipher_bytes)
: clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {}

uint16_t clear_bytes;
uint32_t cipher_bytes;
};
Expand Down
98 changes: 82 additions & 16 deletions packager/media/formats/mp4/encrypting_fragmenter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include "packager/media/formats/mp4/encrypting_fragmenter.h"

#include <limits>

#include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/buffer_reader.h"
#include "packager/media/base/key_source.h"
Expand All @@ -15,33 +17,59 @@
#include "packager/media/filters/vp9_parser.h"
#include "packager/media/formats/mp4/box_definitions.h"

namespace edash_packager {
namespace media {
namespace mp4 {

namespace {
// Generate 64bit IV by default.
const size_t kDefaultIvSize = 8u;
const size_t kCencBlockSize = 16u;
} // namespace

namespace edash_packager {
namespace media {
namespace mp4 {
// Adds one or more subsamples to |*subsamples|. This may add more than one
// if one of the values overflows the integer in the subsample.
void AddSubsamples(uint64_t clear_bytes,
uint64_t cipher_bytes,
std::vector<SubsampleEntry>* subsamples) {
CHECK_LT(cipher_bytes, std::numeric_limits<uint32_t>::max());
const uint64_t kUInt16Max = std::numeric_limits<uint16_t>::max();
while (clear_bytes > kUInt16Max) {
subsamples->push_back(SubsampleEntry(kUInt16Max, 0));
clear_bytes -= kUInt16Max;
}

if (clear_bytes > 0 || cipher_bytes > 0)
subsamples->push_back(SubsampleEntry(clear_bytes, cipher_bytes));
}

VideoCodec GetVideoCodec(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamVideo)
return kUnknownVideoCodec;
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info);
return video_stream_info.codec();
}
} // namespace

EncryptingFragmenter::EncryptingFragmenter(
scoped_refptr<StreamInfo> info,
TrackFragment* traf,
scoped_ptr<EncryptionKey> encryption_key,
int64_t clear_time,
VideoCodec video_codec,
uint8_t nalu_length_size)
int64_t clear_time)
: Fragmenter(traf),
info_(info),
encryption_key_(encryption_key.Pass()),
video_codec_(video_codec),
nalu_length_size_(nalu_length_size),
clear_time_(clear_time) {
DCHECK(encryption_key_);
VideoCodec video_codec = GetVideoCodec(*info);
if (video_codec == kCodecVP8) {
vpx_parser_.reset(new VP8Parser);
} else if (video_codec == kCodecVP9) {
vpx_parser_.reset(new VP9Parser);
} else if (video_codec == kCodecH264) {
header_parser_.reset(new H264VideoSliceHeaderParser);
}
// TODO(modmaker): Support H.265.
}

EncryptingFragmenter::~EncryptingFragmenter() {}
Expand All @@ -66,6 +94,9 @@ Status EncryptingFragmenter::InitializeFragment(int64_t first_sample_dts) {
if (!status.ok())
return status;

if (header_parser_ && !header_parser_->Initialize(info_->extra_data()))
return Status(error::MUXER_FAILURE, "Fail to read SPS and PPS data.");

traf()->auxiliary_size.sample_info_sizes.clear();
traf()->auxiliary_offset.offsets.clear();
if (IsSubsampleEncryptionRequired()) {
Expand Down Expand Up @@ -182,21 +213,43 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
data += frame.frame_size;
}
} else {
NaluReader reader(nalu_length_size_, data, sample->data_size());
NaluReader reader(GetNaluLengthSize(), data, sample->data_size());

// Store the current length of clear data. This is used to squash
// multiple unencrypted NAL units into fewer subsample entries.
uint64_t accumulated_clear_bytes = 0;

Nalu nalu;
NaluReader::Result result;
while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
SubsampleEntry subsample;
subsample.clear_bytes = nalu.header_size();
subsample.cipher_bytes = nalu.data_size();
sample_encryption_entry.subsamples.push_back(subsample);
if (nalu.is_video_slice()) {
// For video-slice NAL units, encrypt the video slice. This skips
// the frame header. If this is an unrecognized codec (e.g. H.265),
// the whole NAL unit will be encrypted.
const int64_t video_slice_header_size =
header_parser_ ? header_parser_->GetHeaderSize(nalu) : 0;
if (video_slice_header_size < 0)
return Status(error::MUXER_FAILURE, "Failed to read slice header.");

const uint64_t current_clear_bytes =
nalu.header_size() + video_slice_header_size;
const uint64_t cipher_bytes =
nalu.data_size() - video_slice_header_size;
const uint8_t* nalu_data = nalu.data() + current_clear_bytes;
EncryptBytes(const_cast<uint8_t*>(nalu_data), cipher_bytes);

EncryptBytes(const_cast<uint8_t*>(nalu.data() + nalu.header_size()),
subsample.cipher_bytes);
AddSubsamples(accumulated_clear_bytes + current_clear_bytes,
cipher_bytes, &sample_encryption_entry.subsamples);
accumulated_clear_bytes = 0;
} else {
// For non-video-slice NAL units, don't encrypt.
accumulated_clear_bytes += nalu.header_size() + nalu.data_size();
}
}
if (result != NaluReader::kEOStream)
return Status(error::MUXER_FAILURE, "Failed to parse NAL units.");
AddSubsamples(accumulated_clear_bytes, 0,
&sample_encryption_entry.subsamples);
}

// The length of per-sample auxiliary datum, defined in CENC ch. 7.
Expand All @@ -212,6 +265,19 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
return Status::OK;
}

uint8_t EncryptingFragmenter::GetNaluLengthSize() {
if (info_->stream_type() != kStreamVideo)
return 0;

const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(*info_);
return video_stream_info.nalu_length_size();
}

bool EncryptingFragmenter::IsSubsampleEncryptionRequired() {
return vpx_parser_ || GetNaluLengthSize() != 0;
}

} // namespace mp4
} // namespace media
} // namespace edash_packager
32 changes: 13 additions & 19 deletions packager/media/formats/mp4/encrypting_fragmenter.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
#ifndef MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
#define MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_

#include "packager/base/memory/ref_counted.h"
#include "packager/base/memory/scoped_ptr.h"
#include "packager/media/filters/vpx_parser.h"
#include "packager/media/formats/mp4/fragmenter.h"
#include "packager/media/formats/mp4/video_slice_header_parser.h"

namespace edash_packager {
namespace media {

class AesCtrEncryptor;
class StreamInfo;
struct EncryptionKey;

namespace mp4 {
Expand All @@ -26,16 +29,10 @@ class EncryptingFragmenter : public Fragmenter {
/// @param encryption_key contains the encryption parameters.
/// @param clear_time specifies clear lead duration in units of the current
/// track's timescale.
/// @param video_codec specifies the codec if input is a video stream; it
/// should be set to kUnknownVideoCodec for audio stream. This
/// parameter is used for proper subsample encryption.
/// @param nalu_length_size specifies the size of NAL unit length, in bytes,
/// for subsample encryption.
EncryptingFragmenter(TrackFragment* traf,
EncryptingFragmenter(scoped_refptr<StreamInfo> info,
TrackFragment* traf,
scoped_ptr<EncryptionKey> encryption_key,
int64_t clear_time,
VideoCodec video_codec,
uint8_t nalu_length_size);
int64_t clear_time);

~EncryptingFragmenter() override;

Expand Down Expand Up @@ -69,23 +66,20 @@ class EncryptingFragmenter : public Fragmenter {
void EncryptBytes(uint8_t* data, uint32_t size);
Status EncryptSample(scoped_refptr<MediaSample> sample);

// If this stream contains AVC, subsample encryption specifies that the size
// and type of NAL units remain unencrypted. This function returns the size of
// the size field in bytes. Can be 1, 2 or 4 bytes.
uint8_t GetNaluLengthSize();
// Should we enable subsample encryption?
bool IsSubsampleEncryptionRequired() {
return vpx_parser_ || nalu_length_size_ != 0;
}
bool IsSubsampleEncryptionRequired();

scoped_refptr<StreamInfo> info_;
scoped_ptr<EncryptionKey> encryption_key_;
scoped_ptr<AesCtrEncryptor> encryptor_;
// For VP8/VP9, uncompressed_header should not be encrypted; for AVC/HEVC,
// the size and type NAL units should not be encrypted.
VideoCodec video_codec_;
// 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_t nalu_length_size_;
int64_t clear_time_;

scoped_ptr<VPxParser> vpx_parser_;
scoped_ptr<VideoSliceHeaderParser> header_parser_;

DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter);
};
Expand Down
10 changes: 4 additions & 6 deletions packager/media/formats/mp4/key_rotation_fragmenter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,17 @@ const bool kInitialEncryptionInfo = false;
} // namespace

KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof,
scoped_refptr<StreamInfo> info,
TrackFragment* traf,
KeySource* encryption_key_source,
KeySource::TrackType track_type,
int64_t crypto_period_duration,
int64_t clear_time,
VideoCodec video_codec,
uint8_t nalu_length_size,
MuxerListener* muxer_listener)
: EncryptingFragmenter(traf,
: EncryptingFragmenter(info,
traf,
scoped_ptr<EncryptionKey>(new EncryptionKey()),
clear_time,
video_codec,
nalu_length_size),
clear_time),
moof_(moof),
encryption_key_source_(encryption_key_source),
track_type_(track_type),
Expand Down
8 changes: 1 addition & 7 deletions packager/media/formats/mp4/key_rotation_fragmenter.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,15 @@ class KeyRotationFragmenter : public EncryptingFragmenter {
/// of the current track's timescale.
/// @param clear_time specifies clear lead duration in units of the current
/// track's timescale.
/// @param video_codec specifies the codec if input is a video stream; it
/// should be set to kUnknownVideoCodec for audio stream. This
/// parameter is used for proper subsample encryption.
/// @param nalu_length_size NAL unit length size, in bytes, for subsample
/// encryption.
/// @param muxer_listener is a pointer to MuxerListener for notifying
/// muxer related events. This may be null.
KeyRotationFragmenter(MovieFragment* moof,
scoped_refptr<StreamInfo> info,
TrackFragment* traf,
KeySource* encryption_key_source,
KeySource::TrackType track_type,
int64_t crypto_period_duration,
int64_t clear_time,
VideoCodec video_codec,
uint8_t nalu_length_size,
MuxerListener* muxer_listener);
~KeyRotationFragmenter() override;

Expand Down
3 changes: 3 additions & 0 deletions packager/media/formats/mp4/mp4.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
'sync_sample_iterator.h',
'track_run_iterator.cc',
'track_run_iterator.h',
'video_slice_header_parser.cc',
'video_slice_header_parser.h',
],
'dependencies': [
'../../../third_party/boringssl/boringssl.gyp:boringssl',
Expand All @@ -74,6 +76,7 @@
'mp4_media_parser_unittest.cc',
'sync_sample_iterator_unittest.cc',
'track_run_iterator_unittest.cc',
'video_slice_header_parser_unittest.cc',
],
'dependencies': [
'../../../testing/gtest.gyp:gtest',
Expand Down
30 changes: 6 additions & 24 deletions packager/media/formats/mp4/segmenter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,6 @@ void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
}
}

VideoCodec GetVideoCodec(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamVideo)
return kUnknownVideoCodec;
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info);
return video_stream_info.codec();
}

uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamVideo)
return 0;
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info);
return video_stream_info.nalu_length_size();
}

KeySource::TrackType GetTrackTypeForEncryption(const StreamInfo& stream_info,
uint32_t max_sd_pixels) {
if (stream_info.stream_type() == kStreamAudio)
Expand Down Expand Up @@ -168,8 +152,6 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
continue;
}

VideoCodec video_codec = GetVideoCodec(*streams[i]->info());
uint8_t nalu_length_size = GetNaluLengthSize(*streams[i]->info());
KeySource::TrackType track_type =
GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels);
SampleDescription& description =
Expand All @@ -191,10 +173,11 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
}

fragmenters_[i] = new KeyRotationFragmenter(
moof_.get(), &moof_->tracks[i], encryption_key_source, track_type,
moof_.get(), streams[i]->info(), &moof_->tracks[i],
encryption_key_source, track_type,
crypto_period_duration_in_seconds * streams[i]->info()->time_scale(),
clear_lead_in_seconds * streams[i]->info()->time_scale(), video_codec,
nalu_length_size, muxer_listener_);
clear_lead_in_seconds * streams[i]->info()->time_scale(),
muxer_listener_);
continue;
}

Expand Down Expand Up @@ -222,9 +205,8 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
}

fragmenters_[i] = new EncryptingFragmenter(
&moof_->tracks[i], encryption_key.Pass(),
clear_lead_in_seconds * streams[i]->info()->time_scale(), video_codec,
nalu_length_size);
streams[i]->info(), &moof_->tracks[i], encryption_key.Pass(),
clear_lead_in_seconds * streams[i]->info()->time_scale());
}

// Choose the first stream if there is no VIDEO.
Expand Down
Loading

0 comments on commit 96abd90

Please # to comment.