Only encrypt video slice NAL units.

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
This commit is contained in:
Jacob Trimble 2016-02-03 14:13:57 -08:00
parent 74951e3405
commit 96abd90ca2
15 changed files with 404 additions and 74 deletions

View File

@ -25,6 +25,11 @@ namespace media {
/// result, and then copying each byte from the decrypted block over the /// result, and then copying each byte from the decrypted block over the
/// corresponding encrypted byte. /// corresponding encrypted byte.
struct SubsampleEntry { 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; uint16_t clear_bytes;
uint32_t cipher_bytes; uint32_t cipher_bytes;
}; };

View File

@ -6,6 +6,8 @@
#include "packager/media/formats/mp4/encrypting_fragmenter.h" #include "packager/media/formats/mp4/encrypting_fragmenter.h"
#include <limits>
#include "packager/media/base/aes_encryptor.h" #include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/buffer_reader.h" #include "packager/media/base/buffer_reader.h"
#include "packager/media/base/key_source.h" #include "packager/media/base/key_source.h"
@ -15,33 +17,59 @@
#include "packager/media/filters/vp9_parser.h" #include "packager/media/filters/vp9_parser.h"
#include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/box_definitions.h"
namespace {
// Generate 64bit IV by default.
const size_t kDefaultIvSize = 8u;
const size_t kCencBlockSize = 16u;
} // namespace
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
namespace mp4 { namespace mp4 {
namespace {
// Generate 64bit IV by default.
const size_t kDefaultIvSize = 8u;
const size_t kCencBlockSize = 16u;
// 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( EncryptingFragmenter::EncryptingFragmenter(
scoped_refptr<StreamInfo> info,
TrackFragment* traf, TrackFragment* traf,
scoped_ptr<EncryptionKey> encryption_key, scoped_ptr<EncryptionKey> encryption_key,
int64_t clear_time, int64_t clear_time)
VideoCodec video_codec,
uint8_t nalu_length_size)
: Fragmenter(traf), : Fragmenter(traf),
info_(info),
encryption_key_(encryption_key.Pass()), encryption_key_(encryption_key.Pass()),
video_codec_(video_codec),
nalu_length_size_(nalu_length_size),
clear_time_(clear_time) { clear_time_(clear_time) {
DCHECK(encryption_key_); DCHECK(encryption_key_);
VideoCodec video_codec = GetVideoCodec(*info);
if (video_codec == kCodecVP8) { if (video_codec == kCodecVP8) {
vpx_parser_.reset(new VP8Parser); vpx_parser_.reset(new VP8Parser);
} else if (video_codec == kCodecVP9) { } else if (video_codec == kCodecVP9) {
vpx_parser_.reset(new VP9Parser); vpx_parser_.reset(new VP9Parser);
} else if (video_codec == kCodecH264) {
header_parser_.reset(new H264VideoSliceHeaderParser);
} }
// TODO(modmaker): Support H.265.
} }
EncryptingFragmenter::~EncryptingFragmenter() {} EncryptingFragmenter::~EncryptingFragmenter() {}
@ -66,6 +94,9 @@ Status EncryptingFragmenter::InitializeFragment(int64_t first_sample_dts) {
if (!status.ok()) if (!status.ok())
return status; 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_size.sample_info_sizes.clear();
traf()->auxiliary_offset.offsets.clear(); traf()->auxiliary_offset.offsets.clear();
if (IsSubsampleEncryptionRequired()) { if (IsSubsampleEncryptionRequired()) {
@ -182,21 +213,43 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
data += frame.frame_size; data += frame.frame_size;
} }
} else { } 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; Nalu nalu;
NaluReader::Result result; NaluReader::Result result;
while ((result = reader.Advance(&nalu)) == NaluReader::kOk) { while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
SubsampleEntry subsample; if (nalu.is_video_slice()) {
subsample.clear_bytes = nalu.header_size(); // For video-slice NAL units, encrypt the video slice. This skips
subsample.cipher_bytes = nalu.data_size(); // the frame header. If this is an unrecognized codec (e.g. H.265),
sample_encryption_entry.subsamples.push_back(subsample); // 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.");
EncryptBytes(const_cast<uint8_t*>(nalu.data() + nalu.header_size()), const uint64_t current_clear_bytes =
subsample.cipher_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);
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) if (result != NaluReader::kEOStream)
return Status(error::MUXER_FAILURE, "Failed to parse NAL units."); 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. // The length of per-sample auxiliary datum, defined in CENC ch. 7.
@ -212,6 +265,19 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
return Status::OK; 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 mp4
} // namespace media } // namespace media
} // namespace edash_packager } // namespace edash_packager

View File

@ -7,14 +7,17 @@
#ifndef MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_ #ifndef MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
#define 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/base/memory/scoped_ptr.h"
#include "packager/media/filters/vpx_parser.h" #include "packager/media/filters/vpx_parser.h"
#include "packager/media/formats/mp4/fragmenter.h" #include "packager/media/formats/mp4/fragmenter.h"
#include "packager/media/formats/mp4/video_slice_header_parser.h"
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
class AesCtrEncryptor; class AesCtrEncryptor;
class StreamInfo;
struct EncryptionKey; struct EncryptionKey;
namespace mp4 { namespace mp4 {
@ -26,16 +29,10 @@ class EncryptingFragmenter : public Fragmenter {
/// @param encryption_key contains the encryption parameters. /// @param encryption_key contains the encryption parameters.
/// @param clear_time specifies clear lead duration in units of the current /// @param clear_time specifies clear lead duration in units of the current
/// track's timescale. /// track's timescale.
/// @param video_codec specifies the codec if input is a video stream; it EncryptingFragmenter(scoped_refptr<StreamInfo> info,
/// should be set to kUnknownVideoCodec for audio stream. This TrackFragment* traf,
/// 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,
scoped_ptr<EncryptionKey> encryption_key, scoped_ptr<EncryptionKey> encryption_key,
int64_t clear_time, int64_t clear_time);
VideoCodec video_codec,
uint8_t nalu_length_size);
~EncryptingFragmenter() override; ~EncryptingFragmenter() override;
@ -69,23 +66,20 @@ class EncryptingFragmenter : public Fragmenter {
void EncryptBytes(uint8_t* data, uint32_t size); void EncryptBytes(uint8_t* data, uint32_t size);
Status EncryptSample(scoped_refptr<MediaSample> sample); 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? // Should we enable subsample encryption?
bool IsSubsampleEncryptionRequired() { bool IsSubsampleEncryptionRequired();
return vpx_parser_ || nalu_length_size_ != 0;
}
scoped_refptr<StreamInfo> info_;
scoped_ptr<EncryptionKey> encryption_key_; scoped_ptr<EncryptionKey> encryption_key_;
scoped_ptr<AesCtrEncryptor> encryptor_; 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_; int64_t clear_time_;
scoped_ptr<VPxParser> vpx_parser_; scoped_ptr<VPxParser> vpx_parser_;
scoped_ptr<VideoSliceHeaderParser> header_parser_;
DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter); DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter);
}; };

View File

@ -18,19 +18,17 @@ const bool kInitialEncryptionInfo = false;
} // namespace } // namespace
KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof, KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof,
scoped_refptr<StreamInfo> info,
TrackFragment* traf, TrackFragment* traf,
KeySource* encryption_key_source, KeySource* encryption_key_source,
KeySource::TrackType track_type, KeySource::TrackType track_type,
int64_t crypto_period_duration, int64_t crypto_period_duration,
int64_t clear_time, int64_t clear_time,
VideoCodec video_codec,
uint8_t nalu_length_size,
MuxerListener* muxer_listener) MuxerListener* muxer_listener)
: EncryptingFragmenter(traf, : EncryptingFragmenter(info,
traf,
scoped_ptr<EncryptionKey>(new EncryptionKey()), scoped_ptr<EncryptionKey>(new EncryptionKey()),
clear_time, clear_time),
video_codec,
nalu_length_size),
moof_(moof), moof_(moof),
encryption_key_source_(encryption_key_source), encryption_key_source_(encryption_key_source),
track_type_(track_type), track_type_(track_type),

View File

@ -31,21 +31,15 @@ class KeyRotationFragmenter : public EncryptingFragmenter {
/// of the current track's timescale. /// of the current track's timescale.
/// @param clear_time specifies clear lead duration in units of the current /// @param clear_time specifies clear lead duration in units of the current
/// track's timescale. /// 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 /// @param muxer_listener is a pointer to MuxerListener for notifying
/// muxer related events. This may be null. /// muxer related events. This may be null.
KeyRotationFragmenter(MovieFragment* moof, KeyRotationFragmenter(MovieFragment* moof,
scoped_refptr<StreamInfo> info,
TrackFragment* traf, TrackFragment* traf,
KeySource* encryption_key_source, KeySource* encryption_key_source,
KeySource::TrackType track_type, KeySource::TrackType track_type,
int64_t crypto_period_duration, int64_t crypto_period_duration,
int64_t clear_time, int64_t clear_time,
VideoCodec video_codec,
uint8_t nalu_length_size,
MuxerListener* muxer_listener); MuxerListener* muxer_listener);
~KeyRotationFragmenter() override; ~KeyRotationFragmenter() override;

View File

@ -52,6 +52,8 @@
'sync_sample_iterator.h', 'sync_sample_iterator.h',
'track_run_iterator.cc', 'track_run_iterator.cc',
'track_run_iterator.h', 'track_run_iterator.h',
'video_slice_header_parser.cc',
'video_slice_header_parser.h',
], ],
'dependencies': [ 'dependencies': [
'../../../third_party/boringssl/boringssl.gyp:boringssl', '../../../third_party/boringssl/boringssl.gyp:boringssl',
@ -74,6 +76,7 @@
'mp4_media_parser_unittest.cc', 'mp4_media_parser_unittest.cc',
'sync_sample_iterator_unittest.cc', 'sync_sample_iterator_unittest.cc',
'track_run_iterator_unittest.cc', 'track_run_iterator_unittest.cc',
'video_slice_header_parser_unittest.cc',
], ],
'dependencies': [ 'dependencies': [
'../../../testing/gtest.gyp:gtest', '../../../testing/gtest.gyp:gtest',

View File

@ -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, KeySource::TrackType GetTrackTypeForEncryption(const StreamInfo& stream_info,
uint32_t max_sd_pixels) { uint32_t max_sd_pixels) {
if (stream_info.stream_type() == kStreamAudio) if (stream_info.stream_type() == kStreamAudio)
@ -168,8 +152,6 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
continue; continue;
} }
VideoCodec video_codec = GetVideoCodec(*streams[i]->info());
uint8_t nalu_length_size = GetNaluLengthSize(*streams[i]->info());
KeySource::TrackType track_type = KeySource::TrackType track_type =
GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels); GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels);
SampleDescription& description = SampleDescription& description =
@ -191,10 +173,11 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
} }
fragmenters_[i] = new KeyRotationFragmenter( 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(), crypto_period_duration_in_seconds * streams[i]->info()->time_scale(),
clear_lead_in_seconds * streams[i]->info()->time_scale(), video_codec, clear_lead_in_seconds * streams[i]->info()->time_scale(),
nalu_length_size, muxer_listener_); muxer_listener_);
continue; continue;
} }
@ -222,9 +205,8 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
} }
fragmenters_[i] = new EncryptingFragmenter( fragmenters_[i] = new EncryptingFragmenter(
&moof_->tracks[i], encryption_key.Pass(), streams[i]->info(), &moof_->tracks[i], encryption_key.Pass(),
clear_lead_in_seconds * streams[i]->info()->time_scale(), video_codec, clear_lead_in_seconds * streams[i]->info()->time_scale());
nalu_length_size);
} }
// Choose the first stream if there is no VIDEO. // Choose the first stream if there is no VIDEO.

View File

@ -0,0 +1,76 @@
// 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/formats/mp4/video_slice_header_parser.h"
#include "packager/media/formats/mp4/rcheck.h"
#include "packager/media/base/buffer_reader.h"
namespace edash_packager {
namespace media {
namespace mp4 {
namespace {
const uint8_t kStartCodeSize = 0;
}
H264VideoSliceHeaderParser::H264VideoSliceHeaderParser() {}
H264VideoSliceHeaderParser::~H264VideoSliceHeaderParser() {}
bool H264VideoSliceHeaderParser::Initialize(
const std::vector<uint8_t>& decoder_configuration) {
// See ISO 14496-15 sec 5.3.3.1.2
BufferReader reader(decoder_configuration.data(),
decoder_configuration.size());
RCHECK(reader.SkipBytes(5));
uint8_t sps_count;
RCHECK(reader.Read1(&sps_count));
sps_count = sps_count & 0x1f;
for (size_t i = 0; i < sps_count; i++) {
uint16_t size;
RCHECK(reader.Read2(&size));
const uint8_t* data = reader.data() + reader.pos();
RCHECK(reader.SkipBytes(size));
int id;
Nalu nalu;
RCHECK(nalu.InitializeFromH264(data, size, kStartCodeSize));
RCHECK(parser_.ParseSPS(nalu, &id) == H264Parser::kOk);
}
uint8_t pps_count;
RCHECK(reader.Read1(&pps_count));
for (size_t i = 0; i < pps_count; i++) {
uint16_t size;
RCHECK(reader.Read2(&size));
const uint8_t* data = reader.data() + reader.pos();
RCHECK(reader.SkipBytes(size));
int id;
Nalu nalu;
RCHECK(nalu.InitializeFromH264(data, size, kStartCodeSize));
RCHECK(parser_.ParsePPS(nalu, &id) == H264Parser::kOk);
}
return true;
}
int64_t H264VideoSliceHeaderParser::GetHeaderSize(const Nalu& nalu) {
DCHECK(nalu.is_video_slice());
H264SliceHeader slice_header;
if (parser_.ParseSliceHeader(nalu, &slice_header) != H264Parser::kOk)
return -1;
// Round-up to bytes.
return (slice_header.header_bit_size - 1) / 8 + 1;
}
} // namespace mp4
} // namespace media
} // namespace edash_packager

View File

@ -0,0 +1,60 @@
// 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_FORMATS_MP4_VIDEO_SLICE_HEADER_PARSER_H_
#define MEDIA_FORMATS_MP4_VIDEO_SLICE_HEADER_PARSER_H_
#include <vector>
#include "packager/media/base/macros.h"
#include "packager/media/filters/h264_parser.h"
namespace edash_packager {
namespace media {
namespace mp4 {
class VideoSliceHeaderParser {
public:
VideoSliceHeaderParser() {}
virtual ~VideoSliceHeaderParser() {}
/// Adds decoder configuration from the given data. This must be called
/// once before any calls to GetHeaderSize.
virtual bool Initialize(
const std::vector<uint8_t>& decoder_configuration) = 0;
/// Gets the header size of the given NALU. Returns < 0 on error.
virtual int64_t GetHeaderSize(const Nalu& nalu) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(VideoSliceHeaderParser);
};
class H264VideoSliceHeaderParser : public VideoSliceHeaderParser {
public:
H264VideoSliceHeaderParser();
~H264VideoSliceHeaderParser() override;
/// @name VideoSliceHeaderParser implementation overrides.
/// @{
bool Initialize(const std::vector<uint8_t>& decoder_configuration) override;
int64_t GetHeaderSize(const Nalu& nalu) override;
/// @}
private:
H264Parser parser_;
DISALLOW_COPY_AND_ASSIGN(H264VideoSliceHeaderParser);
};
// TODO(modmaker): Add H.265 parser.
} // namespace mp4
} // namespace media
} // namespace edash_packager
#endif // MEDIA_FORMATS_MP4_VIDEO_SLICE_HEADER_PARSER_H_

View File

@ -0,0 +1,152 @@
// 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/media/formats/mp4/video_slice_header_parser.h"
namespace edash_packager {
namespace media {
namespace mp4 {
TEST(H264VideoSliceHeaderParserTest, BasicSupport) {
// Taken from bear-640x360.mp4 (video)
const uint8_t kExtraData[] = {
// Header (ignored)
0x01, 0x64, 0x00, 0x1e, 0xff,
// SPS count (ignore top three bits)
0xe1,
// SPS
0x00, 0x19, // Size
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x40, 0xa0,
0x2f, 0xf9, 0x70, 0x11, 0x00, 0x00, 0x03, 0x03,
0xe9, 0x00, 0x00, 0xea, 0x60, 0x0f, 0x16, 0x2d,
0x96,
// PPS count
0x01,
// PPS
0x00, 0x06, // Size
0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0
};
const uint8_t kStartCodeSize = 0;
const uint8_t kData[] = {
// Incomplete data, but we only care about the header size.
0x65, 0x88, 0x84, 0x00, 0x21, 0xff, 0xcf, 0x73, 0xc7, 0x24,
0xc8, 0xc3, 0xa5, 0xcb, 0x77, 0x60, 0x50, 0x85, 0xd9, 0xfc
};
const std::vector<uint8_t> extra_data(kExtraData,
kExtraData + arraysize(kExtraData));
H264VideoSliceHeaderParser parser;
ASSERT_TRUE(parser.Initialize(extra_data));
Nalu nalu;
ASSERT_TRUE(nalu.InitializeFromH264(kData, arraysize(kData), kStartCodeSize));
// Real header size is 34 bits, but we round up to 5 bytes.
EXPECT_EQ(5, parser.GetHeaderSize(nalu));
}
TEST(H264VideoSliceHeaderParserTest, SupportsMultipleEntriesInExtraData) {
const uint8_t kExtraData[] = {
// Header (ignored)
0xfe, 0xed, 0xf0, 0x0d, 0x00,
// SPS count (ignore top three bits)
0xe3,
// SPS
0x00, 0x19, // Size
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x40, 0xa0,
0x2f, 0xf9, 0x70, 0x11, 0x00, 0x00, 0x03, 0x03,
0xe9, 0x00, 0x00, 0xea, 0x60, 0x0f, 0x16, 0x2d,
0x96,
// SPS
0x00, 0x19, // Size
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x40, 0xa0,
0x2f, 0xf9, 0x70, 0x11, 0x00, 0x00, 0x03, 0x03,
0xe9, 0x00, 0x00, 0xea, 0x60, 0x0f, 0x16, 0x2d,
0x96,
// SPS
0x00, 0x19, // Size
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x40, 0xa0,
0x2f, 0xf9, 0x70, 0x11, 0x00, 0x00, 0x03, 0x03,
0xe9, 0x00, 0x00, 0xea, 0x60, 0x0f, 0x16, 0x2d,
0x96,
// PPS count
0x03,
// PPS
0x00, 0x06, // Size
0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0,
// PPS
0x00, 0x06, // Size
0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0,
// PPS
0x00, 0x06, // Size
0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0
};
const std::vector<uint8_t> extra_data(kExtraData,
kExtraData + arraysize(kExtraData));
H264VideoSliceHeaderParser parser;
EXPECT_TRUE(parser.Initialize(extra_data));
}
TEST(H264VideoSliceHeaderParserTest, IgnoresExtraDataAtEnd) {
const uint8_t kExtraData[] = {
// Header (ignored)
0xfe, 0xed, 0xf0, 0x0d, 0x00,
// SPS count
0x00,
// PPS count
0x00,
// Extra data
0x00, 0x19, 0x67, 0x64, 0x00
};
const std::vector<uint8_t> extra_data(kExtraData,
kExtraData + arraysize(kExtraData));
H264VideoSliceHeaderParser parser;
EXPECT_TRUE(parser.Initialize(extra_data));
}
TEST(H264VideoSliceHeaderParserTest, ErrorsForEOSAfterEntry) {
const uint8_t kExtraData[] = {
// Header (ignored)
0xfe, 0xed, 0xf0, 0x0d, 0x00,
// SPS count (ignore top three bits)
0xe3,
// SPS
0x00, 0x19, // Size
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x40, 0xa0,
0x2f, 0xf9, 0x70, 0x11, 0x00, 0x00, 0x03, 0x03,
0xe9, 0x00, 0x00, 0xea, 0x60, 0x0f, 0x16, 0x2d,
0x96,
};
const std::vector<uint8_t> extra_data(kExtraData,
kExtraData + arraysize(kExtraData));
H264VideoSliceHeaderParser parser;
EXPECT_FALSE(parser.Initialize(extra_data));
}
TEST(H264VideoSliceHeaderParserTest, ErrorsForEOSWithinEntry) {
const uint8_t kExtraData[] = {
// Header (ignored)
0xfe, 0xed, 0xf0, 0x0d, 0x00,
// SPS count (ignore top three bits)
0xe3,
// SPS
0x00, 0x19, // Size
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x40, 0xa0,
};
const std::vector<uint8_t> extra_data(kExtraData,
kExtraData + arraysize(kExtraData));
H264VideoSliceHeaderParser parser;
EXPECT_FALSE(parser.Initialize(extra_data));
}
} // namespace mp4
} // namespace media
} // namespace edash_packager