diff --git a/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4 b/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4 index 160d14ebf4..16dd910f4b 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4 and b/packager/app/test/testdata/bear-640x360-v-cenc-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-2.m4s b/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-2.m4s index 6598957aa9..4139342010 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-2.m4s and b/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-2.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-3.m4s b/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-3.m4s index 1772f95e39..1ace57b8d7 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-3.m4s and b/packager/app/test/testdata/bear-640x360-v-live-cenc-golden-3.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-2.m4s b/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-2.m4s index 2d11065919..3b9620c554 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-2.m4s and b/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-2.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-3.m4s b/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-3.m4s index 6174084902..5b12cf215b 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-3.m4s and b/packager/app/test/testdata/bear-640x360-v-live-cenc-rotation-golden-3.m4s differ diff --git a/packager/media/base/decrypt_config.h b/packager/media/base/decrypt_config.h index 3dd5ebcca4..f0921abcb4 100644 --- a/packager/media/base/decrypt_config.h +++ b/packager/media/base/decrypt_config.h @@ -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; }; diff --git a/packager/media/formats/mp4/encrypting_fragmenter.cc b/packager/media/formats/mp4/encrypting_fragmenter.cc index f38b3f0351..4ee46dbd70 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.cc +++ b/packager/media/formats/mp4/encrypting_fragmenter.cc @@ -6,6 +6,8 @@ #include "packager/media/formats/mp4/encrypting_fragmenter.h" +#include + #include "packager/media/base/aes_encryptor.h" #include "packager/media/base/buffer_reader.h" #include "packager/media/base/key_source.h" @@ -15,33 +17,59 @@ #include "packager/media/filters/vp9_parser.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 media { 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* subsamples) { + CHECK_LT(cipher_bytes, std::numeric_limits::max()); + const uint64_t kUInt16Max = std::numeric_limits::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(stream_info); + return video_stream_info.codec(); +} +} // namespace + EncryptingFragmenter::EncryptingFragmenter( + scoped_refptr info, TrackFragment* traf, scoped_ptr 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() {} @@ -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()) { @@ -182,21 +213,43 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr 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."); - EncryptBytes(const_cast(nalu.data() + nalu.header_size()), - subsample.cipher_bytes); + 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(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) 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. @@ -212,6 +265,19 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { return Status::OK; } +uint8_t EncryptingFragmenter::GetNaluLengthSize() { + if (info_->stream_type() != kStreamVideo) + return 0; + + const VideoStreamInfo& video_stream_info = + static_cast(*info_); + return video_stream_info.nalu_length_size(); +} + +bool EncryptingFragmenter::IsSubsampleEncryptionRequired() { + return vpx_parser_ || GetNaluLengthSize() != 0; +} + } // namespace mp4 } // namespace media } // namespace edash_packager diff --git a/packager/media/formats/mp4/encrypting_fragmenter.h b/packager/media/formats/mp4/encrypting_fragmenter.h index 8692000976..031fe32530 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.h +++ b/packager/media/formats/mp4/encrypting_fragmenter.h @@ -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 { @@ -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 info, + TrackFragment* traf, scoped_ptr encryption_key, - int64_t clear_time, - VideoCodec video_codec, - uint8_t nalu_length_size); + int64_t clear_time); ~EncryptingFragmenter() override; @@ -69,23 +66,20 @@ class EncryptingFragmenter : public Fragmenter { void EncryptBytes(uint8_t* data, uint32_t size); Status EncryptSample(scoped_refptr 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 info_; scoped_ptr encryption_key_; scoped_ptr 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 vpx_parser_; + scoped_ptr header_parser_; DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter); }; diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.cc b/packager/media/formats/mp4/key_rotation_fragmenter.cc index d8cbe135db..9922b97f60 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.cc +++ b/packager/media/formats/mp4/key_rotation_fragmenter.cc @@ -18,19 +18,17 @@ const bool kInitialEncryptionInfo = false; } // namespace KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof, + scoped_refptr 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(new EncryptionKey()), - clear_time, - video_codec, - nalu_length_size), + clear_time), moof_(moof), encryption_key_source_(encryption_key_source), track_type_(track_type), diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.h b/packager/media/formats/mp4/key_rotation_fragmenter.h index 2ec49f7228..5bdab76b00 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.h +++ b/packager/media/formats/mp4/key_rotation_fragmenter.h @@ -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 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; diff --git a/packager/media/formats/mp4/mp4.gyp b/packager/media/formats/mp4/mp4.gyp index c26ca025a9..77d8397f2d 100644 --- a/packager/media/formats/mp4/mp4.gyp +++ b/packager/media/formats/mp4/mp4.gyp @@ -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', @@ -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', diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 6008c6cd16..6d23e48172 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -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(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(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) @@ -168,8 +152,6 @@ Status Segmenter::Initialize(const std::vector& 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 = @@ -191,10 +173,11 @@ Status Segmenter::Initialize(const std::vector& 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; } @@ -222,9 +205,8 @@ Status Segmenter::Initialize(const std::vector& 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. diff --git a/packager/media/formats/mp4/video_slice_header_parser.cc b/packager/media/formats/mp4/video_slice_header_parser.cc new file mode 100644 index 0000000000..2350427351 --- /dev/null +++ b/packager/media/formats/mp4/video_slice_header_parser.cc @@ -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& 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 + diff --git a/packager/media/formats/mp4/video_slice_header_parser.h b/packager/media/formats/mp4/video_slice_header_parser.h new file mode 100644 index 0000000000..c78e35b809 --- /dev/null +++ b/packager/media/formats/mp4/video_slice_header_parser.h @@ -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 + +#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& 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& 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_ + diff --git a/packager/media/formats/mp4/video_slice_header_parser_unittest.cc b/packager/media/formats/mp4/video_slice_header_parser_unittest.cc new file mode 100644 index 0000000000..c87d0e5827 --- /dev/null +++ b/packager/media/formats/mp4/video_slice_header_parser_unittest.cc @@ -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 + +#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 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 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 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 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 extra_data(kExtraData, + kExtraData + arraysize(kExtraData)); + + H264VideoSliceHeaderParser parser; + EXPECT_FALSE(parser.Initialize(extra_data)); +} + +} // namespace mp4 +} // namespace media +} // namespace edash_packager