From a5dd1fd3d496a7aca136e0275b96c466f36c0435 Mon Sep 17 00:00:00 2001 From: Kongqun Yang Date: Thu, 23 Mar 2017 11:34:20 -0700 Subject: [PATCH] Allow generating avc3 / hev1 from TS and Wvm files Added a flag --strip_parameter_set_nalus. When enabled, parameter set NAL units, SPS/PPS for H264 and SPS/PPS/VPS for H265, are stripped from frames when converting NAL byte stream (AnnexB stream) to NAL unit stream, which generates avc1/hvc1; otherwise they are not stripped, and avc3/hev1 is generated. Parameter set NAL units should not be stripped if they are varying in the frames otherwise the frames may fail to be decoded. The flag is enabled by default as we don't usually see varying SPS/PPS/VPS and it is more space efficient with them stripped. Set --strip_parameter_set_nalus=false to disable the flag if there are varying SPS/PPS/VPS in the frames. This addresses #206. Change-Id: I34bde6f33069f9f77d51a510b39f58a0f0e141aa --- packager/media/base/fourccs.h | 1 + packager/media/base/stream_info.h | 9 ++-- packager/media/base/video_stream_info.cc | 48 ++++++++++-------- packager/media/base/video_stream_info.h | 30 ++++++++--- packager/media/codecs/codecs.gyp | 1 + .../h264_byte_to_unit_stream_converter.cc | 13 ++++- .../h264_byte_to_unit_stream_converter.h | 7 +++ ..._byte_to_unit_stream_converter_unittest.cc | 28 ++++++++-- .../h265_byte_to_unit_stream_converter.cc | 17 +++++-- .../h265_byte_to_unit_stream_converter.h | 7 +++ ..._byte_to_unit_stream_converter_unittest.cc | 28 ++++++++-- .../h26x_byte_to_unit_stream_converter.cc | 43 +++++++++++++++- .../h26x_byte_to_unit_stream_converter.h | 26 +++++++++- .../hevc_decoder_configuration_record.cc | 17 ++----- .../hevc_decoder_configuration_record.h | 3 +- ...c_decoder_configuration_record_unittest.cc | 4 +- .../media/event/muxer_listener_test_helper.cc | 5 +- packager/media/formats/mp2t/es_parser_h264.cc | 1 + packager/media/formats/mp2t/es_parser_h265.cc | 9 +++- .../mp2t/pes_packet_generator_unittest.cc | 10 ++-- .../formats/mp2t/ts_segmenter_unittest.cc | 31 +++++++---- .../media/formats/mp2t/ts_writer_unittest.cc | 31 +++++++---- packager/media/formats/mp4/box_definitions.cc | 2 + .../formats/mp4/encrypting_fragmenter.cc | 8 +-- .../media/formats/mp4/mp4_media_parser.cc | 27 ++++++++-- packager/media/formats/mp4/mp4_muxer.cc | 28 ++++++---- .../media/formats/webm/segmenter_test_base.cc | 6 +-- .../webm/webm_cluster_parser_unittest.cc | 42 +++++++++++---- .../media/formats/webm/webm_video_client.cc | 7 +-- .../media/formats/wvm/wvm_media_parser.cc | 14 ++--- ...frame.h264 => avc1-unit-stream-frame.h264} | Bin .../test/data/avc3-unit-stream-frame.h264 | Bin 0 -> 5653 bytes .../test/data/hev1-unit-stream-frame.h265 | Bin 0 -> 11403 bytes ...frame.h265 => hvc1-unit-stream-frame.h265} | Bin 34 files changed, 364 insertions(+), 139 deletions(-) rename packager/media/test/data/{avc-unit-stream-frame.h264 => avc1-unit-stream-frame.h264} (100%) create mode 100644 packager/media/test/data/avc3-unit-stream-frame.h264 create mode 100644 packager/media/test/data/hev1-unit-stream-frame.h265 rename packager/media/test/data/{hevc-unit-stream-frame.h265 => hvc1-unit-stream-frame.h265} (100%) diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index 4f4c94b67a..c2dcf409f9 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -22,6 +22,7 @@ enum FourCC : uint32_t { FOURCC_ac_3 = 0x61632d33, // "ac-3" FOURCC_apad = 0x61706164, FOURCC_avc1 = 0x61766331, + FOURCC_avc3 = 0x61766333, FOURCC_avcC = 0x61766343, FOURCC_bloc = 0x626C6F63, FOURCC_cbc1 = 0x63626331, diff --git a/packager/media/base/stream_info.h b/packager/media/base/stream_info.h index 20e4e15a13..16274812a0 100644 --- a/packager/media/base/stream_info.h +++ b/packager/media/base/stream_info.h @@ -25,17 +25,14 @@ enum StreamType { enum Codec { kUnknownCodec = 0, kCodecH264, - kCodecHEV1, - kCodecHVC1, - kCodecVC1, - kCodecMPEG2, - kCodecMPEG4, - kCodecTheora, + kCodecH265, kCodecVP8, kCodecVP9, kCodecVP10, kCodecAAC, kCodecAC3, + // TODO(kqyang): Use kCodecDTS and a kDtsStreamFormat for the various DTS + // streams. kCodecDTSC, kCodecDTSE, kCodecDTSH, diff --git a/packager/media/base/video_stream_info.cc b/packager/media/base/video_stream_info.cc index 81678c1ceb..2e3ee259a5 100644 --- a/packager/media/base/video_stream_info.cc +++ b/packager/media/base/video_stream_info.cc @@ -20,18 +20,8 @@ std::string VideoCodecToString(Codec codec) { switch (codec) { case kCodecH264: return "H264"; - case kCodecHEV1: - return "HEV1"; - case kCodecHVC1: - return "HVC1"; - case kCodecVC1: - return "VC1"; - case kCodecMPEG2: - return "MPEG2"; - case kCodecMPEG4: - return "MPEG4"; - case kCodecTheora: - return "Theora"; + case kCodecH265: + return "H265"; case kCodecVP8: return "VP8"; case kCodecVP9: @@ -46,15 +36,33 @@ std::string VideoCodecToString(Codec codec) { } // namespace -VideoStreamInfo::VideoStreamInfo( - int track_id, uint32_t time_scale, uint64_t duration, Codec codec, - const std::string& codec_string, const uint8_t* codec_config, - size_t codec_config_size, uint16_t width, uint16_t height, - uint32_t pixel_width, uint32_t pixel_height, int16_t trick_play_rate, - uint8_t nalu_length_size, const std::string& language, bool is_encrypted) - : StreamInfo(kStreamVideo, track_id, time_scale, duration, codec, - codec_string, codec_config, codec_config_size, language, +VideoStreamInfo::VideoStreamInfo(int track_id, + uint32_t time_scale, + uint64_t duration, + Codec codec, + H26xStreamFormat h26x_stream_format, + const std::string& codec_string, + const uint8_t* codec_config, + size_t codec_config_size, + uint16_t width, + uint16_t height, + uint32_t pixel_width, + uint32_t pixel_height, + int16_t trick_play_rate, + uint8_t nalu_length_size, + const std::string& language, + bool is_encrypted) + : StreamInfo(kStreamVideo, + track_id, + time_scale, + duration, + codec, + codec_string, + codec_config, + codec_config_size, + language, is_encrypted), + h26x_stream_format_(h26x_stream_format), width_(width), height_(height), pixel_width_(pixel_width), diff --git a/packager/media/base/video_stream_info.h b/packager/media/base/video_stream_info.h index b375689b94..8989a65f89 100644 --- a/packager/media/base/video_stream_info.h +++ b/packager/media/base/video_stream_info.h @@ -12,18 +12,34 @@ namespace shaka { namespace media { +enum class H26xStreamFormat { + kUnSpecified, + kAnnexbByteStream, + kNalUnitStreamWithParameterSetNalus, + kNalUnitStreamWithoutParameterSetNalus, +}; + /// Holds video stream information. class VideoStreamInfo : public StreamInfo { public: /// Construct an initialized video stream info object. /// @param pixel_width is the width of the pixel. 0 if unknown. /// @param pixel_height is the height of the pixels. 0 if unknown. - VideoStreamInfo(int track_id, uint32_t time_scale, uint64_t duration, - Codec codec, const std::string& codec_string, - const uint8_t* codec_config, size_t codec_config_size, - uint16_t width, uint16_t height, uint32_t pixel_width, - uint32_t pixel_height, int16_t trick_play_rate, - uint8_t nalu_length_size, const std::string& language, + VideoStreamInfo(int track_id, + uint32_t time_scale, + uint64_t duration, + Codec codec, + H26xStreamFormat h26x_stream_format, + const std::string& codec_string, + const uint8_t* codec_config, + size_t codec_config_size, + uint16_t width, + uint16_t height, + uint32_t pixel_width, + uint32_t pixel_height, + int16_t trick_play_rate, + uint8_t nalu_length_size, + const std::string& language, bool is_encrypted); /// @name StreamInfo implementation overrides. @@ -32,6 +48,7 @@ class VideoStreamInfo : public StreamInfo { std::string ToString() const override; /// @} + H26xStreamFormat h26x_stream_format() const { return h26x_stream_format_; } uint16_t width() const { return width_; } uint16_t height() const { return height_; } /// Returns the pixel width. @@ -56,6 +73,7 @@ class VideoStreamInfo : public StreamInfo { private: ~VideoStreamInfo() override; + H26xStreamFormat h26x_stream_format_; uint16_t width_; uint16_t height_; diff --git a/packager/media/codecs/codecs.gyp b/packager/media/codecs/codecs.gyp index 477e2a295a..f4c9b313b7 100644 --- a/packager/media/codecs/codecs.gyp +++ b/packager/media/codecs/codecs.gyp @@ -53,6 +53,7 @@ ], 'dependencies': [ '../../base/base.gyp:base', + '../../third_party/gflags/gflags.gyp:gflags', ], }, { diff --git a/packager/media/codecs/h264_byte_to_unit_stream_converter.cc b/packager/media/codecs/h264_byte_to_unit_stream_converter.cc index 9de79e9a79..cdf54928b1 100644 --- a/packager/media/codecs/h264_byte_to_unit_stream_converter.cc +++ b/packager/media/codecs/h264_byte_to_unit_stream_converter.cc @@ -17,6 +17,11 @@ namespace media { H264ByteToUnitStreamConverter::H264ByteToUnitStreamConverter() : H26xByteToUnitStreamConverter(Nalu::kH264) {} + +H264ByteToUnitStreamConverter::H264ByteToUnitStreamConverter( + H26xStreamFormat stream_format) + : H26xByteToUnitStreamConverter(Nalu::kH264, stream_format) {} + H264ByteToUnitStreamConverter::~H264ByteToUnitStreamConverter() {} bool H264ByteToUnitStreamConverter::GetDecoderConfigurationRecord( @@ -60,13 +65,17 @@ bool H264ByteToUnitStreamConverter::ProcessNalu(const Nalu& nalu) { switch (nalu.type()) { case Nalu::H264_SPS: + if (strip_parameter_set_nalus()) + WarnIfNotMatch(nalu.type(), nalu_ptr, nalu_size, last_sps_); // Grab SPS NALU. last_sps_.assign(nalu_ptr, nalu_ptr + nalu_size); - return true; + return strip_parameter_set_nalus(); case Nalu::H264_PPS: + if (strip_parameter_set_nalus()) + WarnIfNotMatch(nalu.type(), nalu_ptr, nalu_size, last_pps_); // Grab PPS NALU. last_pps_.assign(nalu_ptr, nalu_ptr + nalu_size); - return true; + return strip_parameter_set_nalus(); case Nalu::H264_AUD: // Ignore AUD NALU. return true; diff --git a/packager/media/codecs/h264_byte_to_unit_stream_converter.h b/packager/media/codecs/h264_byte_to_unit_stream_converter.h index 5b177eeee2..79074eadf8 100644 --- a/packager/media/codecs/h264_byte_to_unit_stream_converter.h +++ b/packager/media/codecs/h264_byte_to_unit_stream_converter.h @@ -21,7 +21,14 @@ namespace media { /// Annex B) into H.264 NAL unit streams (as specified in ISO/IEC 14496-15). class H264ByteToUnitStreamConverter : public H26xByteToUnitStreamConverter { public: + /// Create a H264 byte to unit stream converter. + /// The setting of @a KeepParameterSetNalus is defined by a gflag. H264ByteToUnitStreamConverter(); + + /// Create a H264 byte to unit stream converter with desired output stream + /// format (whether to include parameter set nal units). + explicit H264ByteToUnitStreamConverter(H26xStreamFormat stream_format); + ~H264ByteToUnitStreamConverter() override; /// @name H26xByteToUnitStreamConverter implementation override. diff --git a/packager/media/codecs/h264_byte_to_unit_stream_converter_unittest.cc b/packager/media/codecs/h264_byte_to_unit_stream_converter_unittest.cc index eeb54c3ded..5fcfeb324d 100644 --- a/packager/media/codecs/h264_byte_to_unit_stream_converter_unittest.cc +++ b/packager/media/codecs/h264_byte_to_unit_stream_converter_unittest.cc @@ -20,16 +20,17 @@ const char kExpectedConfigRecord[] = namespace shaka { namespace media { -TEST(H264ByteToUnitStreamConverter, ConversionSuccess) { +TEST(H264ByteToUnitStreamConverter, StripParameterSetsNalu) { std::vector input_frame = ReadTestDataFile("avc-byte-stream-frame.h264"); ASSERT_FALSE(input_frame.empty()); std::vector expected_output_frame = - ReadTestDataFile("avc-unit-stream-frame.h264"); + ReadTestDataFile("avc1-unit-stream-frame.h264"); ASSERT_FALSE(expected_output_frame.empty()); - H264ByteToUnitStreamConverter converter; + H264ByteToUnitStreamConverter converter( + H26xStreamFormat::kNalUnitStreamWithoutParameterSetNalus); std::vector output_frame; ASSERT_TRUE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), input_frame.size(), @@ -44,10 +45,29 @@ TEST(H264ByteToUnitStreamConverter, ConversionSuccess) { EXPECT_EQ(expected_decoder_config, decoder_config); } +TEST(H264ByteToUnitStreamConverter, KeepParameterSetsNalu) { + std::vector input_frame = + ReadTestDataFile("avc-byte-stream-frame.h264"); + ASSERT_FALSE(input_frame.empty()); + + std::vector expected_output_frame = + ReadTestDataFile("avc3-unit-stream-frame.h264"); + ASSERT_FALSE(expected_output_frame.empty()); + + H264ByteToUnitStreamConverter converter( + H26xStreamFormat::kNalUnitStreamWithParameterSetNalus); + std::vector output_frame; + ASSERT_TRUE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), + input_frame.size(), + &output_frame)); + EXPECT_EQ(expected_output_frame, output_frame); +} + TEST(H264ByteToUnitStreamConverter, ConversionFailure) { std::vector input_frame(100, 0); - H264ByteToUnitStreamConverter converter; + H264ByteToUnitStreamConverter converter( + H26xStreamFormat::kNalUnitStreamWithParameterSetNalus); std::vector output_frame; EXPECT_FALSE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), 0, diff --git a/packager/media/codecs/h265_byte_to_unit_stream_converter.cc b/packager/media/codecs/h265_byte_to_unit_stream_converter.cc index 17da8f37f6..5c62547cc0 100644 --- a/packager/media/codecs/h265_byte_to_unit_stream_converter.cc +++ b/packager/media/codecs/h265_byte_to_unit_stream_converter.cc @@ -18,6 +18,11 @@ namespace media { H265ByteToUnitStreamConverter::H265ByteToUnitStreamConverter() : H26xByteToUnitStreamConverter(Nalu::kH265) {} + +H265ByteToUnitStreamConverter::H265ByteToUnitStreamConverter( + H26xStreamFormat stream_format) + : H26xByteToUnitStreamConverter(Nalu::kH265, stream_format) {} + H265ByteToUnitStreamConverter::~H265ByteToUnitStreamConverter() {} bool H265ByteToUnitStreamConverter::GetDecoderConfigurationRecord( @@ -100,17 +105,23 @@ bool H265ByteToUnitStreamConverter::ProcessNalu(const Nalu& nalu) { switch (nalu.type()) { case Nalu::H265_SPS: + if (strip_parameter_set_nalus()) + WarnIfNotMatch(nalu.type(), nalu_ptr, nalu_size, last_sps_); // Grab SPS NALU. last_sps_.assign(nalu_ptr, nalu_ptr + nalu_size); - return true; + return strip_parameter_set_nalus(); case Nalu::H265_PPS: + if (strip_parameter_set_nalus()) + WarnIfNotMatch(nalu.type(), nalu_ptr, nalu_size, last_pps_); // Grab PPS NALU. last_pps_.assign(nalu_ptr, nalu_ptr + nalu_size); - return true; + return strip_parameter_set_nalus(); case Nalu::H265_VPS: + if (strip_parameter_set_nalus()) + WarnIfNotMatch(nalu.type(), nalu_ptr, nalu_size, last_vps_); // Grab VPS NALU. last_vps_.assign(nalu_ptr, nalu_ptr + nalu_size); - return true; + return strip_parameter_set_nalus(); case Nalu::H265_AUD: // Ignore AUD NALU. return true; diff --git a/packager/media/codecs/h265_byte_to_unit_stream_converter.h b/packager/media/codecs/h265_byte_to_unit_stream_converter.h index d9f87966ad..889e86bd2c 100644 --- a/packager/media/codecs/h265_byte_to_unit_stream_converter.h +++ b/packager/media/codecs/h265_byte_to_unit_stream_converter.h @@ -21,7 +21,14 @@ namespace media { /// Annex B) into H.265 NAL unit streams (as specified in ISO/IEC 14496-15). class H265ByteToUnitStreamConverter : public H26xByteToUnitStreamConverter { public: + /// Create a H265 byte to unit stream converter. + /// The setting of @a KeepParameterSetNalus is defined by a gflag. H265ByteToUnitStreamConverter(); + + /// Create a H265 byte to unit stream converter with desired output stream + /// format (whether to include parameter set nal units). + explicit H265ByteToUnitStreamConverter(H26xStreamFormat stream_format); + ~H265ByteToUnitStreamConverter() override; /// @name H26xByteToUnitStreamConverter implementation override. diff --git a/packager/media/codecs/h265_byte_to_unit_stream_converter_unittest.cc b/packager/media/codecs/h265_byte_to_unit_stream_converter_unittest.cc index bfa9976d01..682085fae3 100644 --- a/packager/media/codecs/h265_byte_to_unit_stream_converter_unittest.cc +++ b/packager/media/codecs/h265_byte_to_unit_stream_converter_unittest.cc @@ -23,16 +23,17 @@ const char kExpectedConfigRecord[] = namespace shaka { namespace media { -TEST(H265ByteToUnitStreamConverter, ConversionSuccess) { +TEST(H265ByteToUnitStreamConverter, StripParameterSetsNalu) { std::vector input_frame = ReadTestDataFile("hevc-byte-stream-frame.h265"); ASSERT_FALSE(input_frame.empty()); std::vector expected_output_frame = - ReadTestDataFile("hevc-unit-stream-frame.h265"); + ReadTestDataFile("hvc1-unit-stream-frame.h265"); ASSERT_FALSE(expected_output_frame.empty()); - H265ByteToUnitStreamConverter converter; + H265ByteToUnitStreamConverter converter( + H26xStreamFormat::kNalUnitStreamWithoutParameterSetNalus); std::vector output_frame; ASSERT_TRUE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), input_frame.size(), @@ -56,10 +57,29 @@ TEST(H265ByteToUnitStreamConverter, ConversionSuccess) { EXPECT_EQ(Nalu::H265_VPS, config.nalu(2).type()); } +TEST(H265ByteToUnitStreamConverter, KeepParameterSetsNalu) { + std::vector input_frame = + ReadTestDataFile("hevc-byte-stream-frame.h265"); + ASSERT_FALSE(input_frame.empty()); + + std::vector expected_output_frame = + ReadTestDataFile("hev1-unit-stream-frame.h265"); + ASSERT_FALSE(expected_output_frame.empty()); + + H265ByteToUnitStreamConverter converter( + H26xStreamFormat::kNalUnitStreamWithParameterSetNalus); + std::vector output_frame; + ASSERT_TRUE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), + input_frame.size(), + &output_frame)); + EXPECT_EQ(expected_output_frame, output_frame); +} + TEST(H265ByteToUnitStreamConverter, ConversionFailure) { std::vector input_frame(100, 0); - H265ByteToUnitStreamConverter converter; + H265ByteToUnitStreamConverter converter( + H26xStreamFormat::kNalUnitStreamWithParameterSetNalus); std::vector output_frame; EXPECT_FALSE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), 0, diff --git a/packager/media/codecs/h26x_byte_to_unit_stream_converter.cc b/packager/media/codecs/h26x_byte_to_unit_stream_converter.cc index 98b7169872..ffceee182b 100644 --- a/packager/media/codecs/h26x_byte_to_unit_stream_converter.cc +++ b/packager/media/codecs/h26x_byte_to_unit_stream_converter.cc @@ -6,8 +6,21 @@ #include "packager/media/codecs/h26x_byte_to_unit_stream_converter.h" +#include #include +#include "packager/base/strings/string_number_conversions.h" + +// TODO(kqyang): Move byte to unit stream convertion to muxer and make it a +// muxer option. +DEFINE_bool(strip_parameter_set_nalus, + true, + "When converting from NAL byte stream (AnnexB stream) to NAL unit " + "stream, this flag determines whether to strip parameter sets NAL " + "units, i.e. SPS/PPS for H264 and SPS/PPS/VPS for H265, from the " + "frames. Note that avc1/hvc1 is generated if this flag is enabled; " + "otherwise avc3/hev1 is generated."); + #include "packager/base/logging.h" #include "packager/media/base/buffer_writer.h" @@ -22,7 +35,17 @@ const size_t kStreamConversionOverhead = 100; H26xByteToUnitStreamConverter::H26xByteToUnitStreamConverter( Nalu::CodecType type) - : type_(type) {} + : type_(type), + stream_format_( + FLAGS_strip_parameter_set_nalus + ? H26xStreamFormat::kNalUnitStreamWithoutParameterSetNalus + : H26xStreamFormat::kNalUnitStreamWithParameterSetNalus) {} + +H26xByteToUnitStreamConverter::H26xByteToUnitStreamConverter( + Nalu::CodecType type, + H26xStreamFormat stream_format) + : type_(type), stream_format_(stream_format) {} + H26xByteToUnitStreamConverter::~H26xByteToUnitStreamConverter() {} bool H26xByteToUnitStreamConverter::ConvertByteStreamToNalUnitStream( @@ -57,6 +80,22 @@ bool H26xByteToUnitStreamConverter::ConvertByteStreamToNalUnitStream( return true; } +void H26xByteToUnitStreamConverter::WarnIfNotMatch( + int nalu_type, + const uint8_t* nalu_ptr, + size_t nalu_size, + const std::vector& vector) { + if (vector.empty()) + return; + if (vector.size() != nalu_size || + memcmp(vector.data(), nalu_ptr, nalu_size) != 0) { + LOG(WARNING) << "Seeing varying NAL unit of type " << nalu_type + << ". You may need to set --strip_parameter_set_nalus=false " + "during packaging to generate a playable stream."; + VLOG(1) << "Old: " << base::HexEncode(vector.data(), vector.size()); + VLOG(1) << "New: " << base::HexEncode(nalu_ptr, nalu_size); + } +} + } // namespace media } // namespace shaka - diff --git a/packager/media/codecs/h26x_byte_to_unit_stream_converter.h b/packager/media/codecs/h26x_byte_to_unit_stream_converter.h index b43da69528..5762e9bd6f 100644 --- a/packager/media/codecs/h26x_byte_to_unit_stream_converter.h +++ b/packager/media/codecs/h26x_byte_to_unit_stream_converter.h @@ -11,6 +11,7 @@ #include +#include "packager/media/base/video_stream_info.h" #include "packager/media/codecs/nalu_reader.h" namespace shaka { @@ -23,7 +24,15 @@ class H26xByteToUnitStreamConverter { public: static const size_t kUnitStreamNaluLengthSize = 4; - H26xByteToUnitStreamConverter(Nalu::CodecType type); + /// Create a byte to unit stream converter with specified codec type. + /// The setting of @a KeepParameterSetNalus is defined by a gflag. + explicit H26xByteToUnitStreamConverter(Nalu::CodecType type); + + /// Create a byte to unit stream converter with specified codec type and + /// desired output stream format (whether to include parameter set nal units). + H26xByteToUnitStreamConverter(Nalu::CodecType type, + H26xStreamFormat stream_format); + virtual ~H26xByteToUnitStreamConverter(); /// Converts a whole byte stream encoded video frame to NAL unit stream @@ -47,12 +56,27 @@ class H26xByteToUnitStreamConverter { virtual bool GetDecoderConfigurationRecord( std::vector* decoder_config) const = 0; + H26xStreamFormat stream_format() const { return stream_format_; } + + protected: + bool strip_parameter_set_nalus() const { + return stream_format_ == + H26xStreamFormat::kNalUnitStreamWithoutParameterSetNalus; + } + + // Warn if (nalu_ptr, nalu_size) does not match with |vector|. + void WarnIfNotMatch(int nalu_type, + const uint8_t* nalu_ptr, + size_t nalu_size, + const std::vector& vector); + private: // Process the given Nalu. If this returns true, it was handled and should // not be copied to the buffer. virtual bool ProcessNalu(const Nalu& nalu) = 0; Nalu::CodecType type_; + H26xStreamFormat stream_format_; DISALLOW_COPY_AND_ASSIGN(H26xByteToUnitStreamConverter); }; diff --git a/packager/media/codecs/hevc_decoder_configuration_record.cc b/packager/media/codecs/hevc_decoder_configuration_record.cc index 43ba8e0bc2..c6037a4f43 100644 --- a/packager/media/codecs/hevc_decoder_configuration_record.cc +++ b/packager/media/codecs/hevc_decoder_configuration_record.cc @@ -59,18 +59,6 @@ std::string ReverseBitsAndHexEncode(uint32_t x) { return TrimLeadingZeros(base::HexEncode(bytes, arraysize(bytes))); } -std::string CodecAsString(Codec codec) { - switch (codec) { - case kCodecHEV1: - return "hev1"; - case kCodecHVC1: - return "hvc1"; - default: - LOG(WARNING) << "Unknown codec: " << codec; - return std::string(); - } -} - } // namespace HEVCDecoderConfigurationRecord::HEVCDecoderConfigurationRecord() @@ -132,10 +120,11 @@ bool HEVCDecoderConfigurationRecord::ParseInternal() { return true; } -std::string HEVCDecoderConfigurationRecord::GetCodecString(Codec codec) const { +std::string HEVCDecoderConfigurationRecord::GetCodecString( + FourCC codec_fourcc) const { // ISO/IEC 14496-15:2014 Annex E. std::vector fields; - fields.push_back(CodecAsString(codec)); + fields.push_back(FourCCToString(codec_fourcc)); fields.push_back(GeneralProfileSpaceAsString(general_profile_space_) + base::IntToString(general_profile_idc_)); fields.push_back( diff --git a/packager/media/codecs/hevc_decoder_configuration_record.h b/packager/media/codecs/hevc_decoder_configuration_record.h index 6f3c3e64ce..5b94f77645 100644 --- a/packager/media/codecs/hevc_decoder_configuration_record.h +++ b/packager/media/codecs/hevc_decoder_configuration_record.h @@ -12,6 +12,7 @@ #include #include "packager/base/macros.h" +#include "packager/media/base/fourccs.h" #include "packager/media/base/video_stream_info.h" #include "packager/media/codecs/decoder_configuration_record.h" @@ -25,7 +26,7 @@ class HEVCDecoderConfigurationRecord : public DecoderConfigurationRecord { ~HEVCDecoderConfigurationRecord() override; /// @return The codec string. - std::string GetCodecString(Codec codec) const; + std::string GetCodecString(FourCC codec_fourcc) const; private: bool ParseInternal() override; diff --git a/packager/media/codecs/hevc_decoder_configuration_record_unittest.cc b/packager/media/codecs/hevc_decoder_configuration_record_unittest.cc index 722a89f8cb..fededbca84 100644 --- a/packager/media/codecs/hevc_decoder_configuration_record_unittest.cc +++ b/packager/media/codecs/hevc_decoder_configuration_record_unittest.cc @@ -45,8 +45,8 @@ TEST(HEVCDecoderConfigurationRecordTest, Success) { EXPECT_EQ(4u, hevc_config.nalu_length_size()); - EXPECT_EQ("hev1.2.4.L63.90", hevc_config.GetCodecString(kCodecHEV1)); - EXPECT_EQ("hvc1.2.4.L63.90", hevc_config.GetCodecString(kCodecHVC1)); + EXPECT_EQ("hev1.2.4.L63.90", hevc_config.GetCodecString(FOURCC_hev1)); + EXPECT_EQ("hvc1.2.4.L63.90", hevc_config.GetCodecString(FOURCC_hvc1)); EXPECT_EQ(2u, hevc_config.nalu_count()); EXPECT_EQ(0x16u, hevc_config.nalu(0).payload_size()); diff --git a/packager/media/event/muxer_listener_test_helper.cc b/packager/media/event/muxer_listener_test_helper.cc index 38b0f83f06..e22209799b 100644 --- a/packager/media/event/muxer_listener_test_helper.cc +++ b/packager/media/event/muxer_listener_test_helper.cc @@ -18,8 +18,9 @@ scoped_refptr CreateVideoStreamInfo( const VideoStreamInfoParameters& param) { return scoped_refptr(new VideoStreamInfo( param.track_id, param.time_scale, param.duration, param.codec, - param.codec_string, param.codec_config.data(), param.codec_config.size(), - param.width, param.height, param.pixel_width, param.pixel_height, + H26xStreamFormat::kUnSpecified, param.codec_string, + param.codec_config.data(), param.codec_config.size(), param.width, + param.height, param.pixel_width, param.pixel_height, 0, // trick_play_rate param.nalu_length_size, param.language, param.is_encrypted)); } diff --git a/packager/media/formats/mp2t/es_parser_h264.cc b/packager/media/formats/mp2t/es_parser_h264.cc index ccc8da4493..1a84ba7daf 100644 --- a/packager/media/formats/mp2t/es_parser_h264.cc +++ b/packager/media/formats/mp2t/es_parser_h264.cc @@ -148,6 +148,7 @@ bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) { last_video_decoder_config_ = scoped_refptr(new VideoStreamInfo( pid(), kMpeg2Timescale, kInfiniteDuration, kCodecH264, + stream_converter()->stream_format(), AVCDecoderConfigurationRecord::GetCodecString(decoder_config_record[1], decoder_config_record[2], decoder_config_record[3]), diff --git a/packager/media/formats/mp2t/es_parser_h265.cc b/packager/media/formats/mp2t/es_parser_h265.cc index 6318700fc2..1e38740b2b 100644 --- a/packager/media/formats/mp2t/es_parser_h265.cc +++ b/packager/media/formats/mp2t/es_parser_h265.cc @@ -151,9 +151,14 @@ bool EsParserH265::UpdateVideoDecoderConfig(int pps_id) { return false; } + const H26xStreamFormat stream_format = stream_converter()->stream_format(); + const FourCC codec_fourcc = + stream_format == H26xStreamFormat::kNalUnitStreamWithParameterSetNalus + ? FOURCC_hev1 + : FOURCC_hvc1; last_video_decoder_config_ = scoped_refptr(new VideoStreamInfo( - pid(), kMpeg2Timescale, kInfiniteDuration, kCodecHVC1, - decoder_config.GetCodecString(kCodecHVC1), decoder_config_record.data(), + pid(), kMpeg2Timescale, kInfiniteDuration, kCodecH265, stream_format, + decoder_config.GetCodecString(codec_fourcc), decoder_config_record.data(), decoder_config_record.size(), coded_width, coded_height, pixel_width, pixel_height, 0, H26xByteToUnitStreamConverter::kUnitStreamNaluLengthSize, std::string(), false)); diff --git a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc index 94fc32bdaf..dcef7a0a02 100644 --- a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc +++ b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc @@ -104,7 +104,8 @@ class MockAACAudioSpecificConfig : public AACAudioSpecificConfig { scoped_refptr CreateVideoStreamInfo(Codec codec) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, codec, kCodecString, kVideoExtraData, + kTrackId, kTimeScale, kDuration, codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kVideoExtraData, arraysize(kVideoExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); return stream_info; @@ -363,9 +364,10 @@ TEST_F(PesPacketGeneratorTest, AddAudioSampleFailedToConvert) { TEST_F(PesPacketGeneratorTest, TimeStampScaling) { const uint32_t kTestTimescale = 1000; scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTestTimescale, kDuration, kH264Codec, kCodecString, - kVideoExtraData, arraysize(kVideoExtraData), kWidth, kHeight, kPixelWidth, - kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTrackId, kTestTimescale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kVideoExtraData, + arraysize(kVideoExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, + kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(generator_.Initialize(*stream_info)); EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index d2f1e240de..3c705ab014 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -109,7 +109,8 @@ class TsSegmenterTest : public ::testing::Test { TEST_F(TsSegmenterTest, Initialize) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; @@ -129,7 +130,8 @@ TEST_F(TsSegmenterTest, Initialize) { TEST_F(TsSegmenterTest, AddSample) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; @@ -184,9 +186,10 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // done correctly in the segmenter. const uint32_t kInputTimescale = 1001; scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kInputTimescale, kDuration, kH264Codec, kCodecString, - kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, - kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTrackId, kInputTimescale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, + arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, + kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; @@ -283,7 +286,8 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // Finalize right after Initialize(). The writer will not be initialized. TEST_F(TsSegmenterTest, InitializeThenFinalize) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; @@ -312,7 +316,8 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { // writer with a mock. TEST_F(TsSegmenterTest, Finalize) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; @@ -341,7 +346,8 @@ TEST_F(TsSegmenterTest, Finalize) { // Verify that it won't finish a segment if the sample is not a key frame. TEST_F(TsSegmenterTest, SegmentOnlyBeforeKeyFrame) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; @@ -447,7 +453,8 @@ TEST_F(TsSegmenterTest, SegmentOnlyBeforeKeyFrame) { TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; @@ -490,7 +497,8 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) { // not null. TEST_F(TsSegmenterTest, WithEncryptionNoClearLeadNoMuxerListener) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; @@ -530,7 +538,8 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLeadNoMuxerListener) { // Verify that encryption notification is sent to objects after clear lead. TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; diff --git a/packager/media/formats/mp2t/ts_writer_unittest.cc b/packager/media/formats/mp2t/ts_writer_unittest.cc index b632d7bec9..a2a8330c29 100644 --- a/packager/media/formats/mp2t/ts_writer_unittest.cc +++ b/packager/media/formats/mp2t/ts_writer_unittest.cc @@ -158,7 +158,8 @@ class TsWriterTest : public ::testing::Test { TEST_F(TsWriterTest, InitializeVideoH264) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); @@ -166,9 +167,10 @@ TEST_F(TsWriterTest, InitializeVideoH264) { TEST_F(TsWriterTest, InitializeVideoNonH264) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, Codec::kCodecVP9, kCodecString, - kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, - kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTrackId, kTimeScale, kDuration, Codec::kCodecVP9, + H26xStreamFormat::kUnSpecified, kCodecString, kExtraData, + arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, + kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_FALSE(ts_writer_.Initialize(*stream_info)); } @@ -199,7 +201,8 @@ TEST_F(TsWriterTest, ClearH264Psi) { EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(WriteOnePmt()); scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); @@ -281,7 +284,8 @@ TEST_F(TsWriterTest, ClearLeadH264Pmt) { .WillOnce(WriteTwoPmts()); scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); @@ -310,7 +314,8 @@ TEST_F(TsWriterTest, EncryptedSegmentsH264Pmt) { EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(WriteOnePmt()); scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); @@ -398,7 +403,8 @@ TEST_F(TsWriterTest, EncryptedSegmentsAacPmt) { TEST_F(TsWriterTest, AddPesPacket) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); @@ -463,7 +469,8 @@ TEST_F(TsWriterTest, AddPesPacket) { // Verify that PES packet > 64KiB can be handled. TEST_F(TsWriterTest, BigPesPacket) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); @@ -499,7 +506,8 @@ TEST_F(TsWriterTest, BigPesPacket) { // PTS (implicilty) cast to bool is true. TEST_F(TsWriterTest, PesPtsZeroNoDts) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); @@ -559,7 +567,8 @@ TEST_F(TsWriterTest, PesPtsZeroNoDts) { // adaptation_field_length should be 0. TEST_F(TsWriterTest, TsPacketPayload183Bytes) { scoped_refptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, + kTrackId, kTimeScale, kDuration, kH264Codec, + H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 99d08c0caa..9b5332ed5d 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -1509,6 +1509,7 @@ bool VideoSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { const FourCC actual_format = GetActualFormat(); switch (actual_format) { case FOURCC_avc1: + case FOURCC_avc3: compressor_name.assign( kAvcCompressorName, kAvcCompressorName + arraysize(kAvcCompressorName)); @@ -1595,6 +1596,7 @@ size_t VideoSampleEntry::ComputeSizeInternal() { FourCC VideoSampleEntry::GetCodecConfigurationBoxType(FourCC format) const { switch (format) { case FOURCC_avc1: + case FOURCC_avc3: return FOURCC_avcC; case FOURCC_hev1: case FOURCC_hvc1: diff --git a/packager/media/formats/mp4/encrypting_fragmenter.cc b/packager/media/formats/mp4/encrypting_fragmenter.cc index 859ec8104c..15a2fd2911 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.cc +++ b/packager/media/formats/mp4/encrypting_fragmenter.cc @@ -88,9 +88,7 @@ EncryptingFragmenter::EncryptingFragmenter( case kCodecH264: header_parser_.reset(new H264VideoSliceHeaderParser); break; - case kCodecHVC1: - FALLTHROUGH_INTENDED; - case kCodecHEV1: + case kCodecH265: header_parser_.reset(new H265VideoSliceHeaderParser); break; default: @@ -304,9 +302,7 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { } } else { const Nalu::CodecType nalu_type = - (video_codec_ == kCodecHVC1 || video_codec_ == kCodecHEV1) - ? Nalu::kH265 - : Nalu::kH264; + (video_codec_ == kCodecH265) ? Nalu::kH265 : Nalu::kH264; NaluReader reader(nalu_type, nalu_length_size_, data, sample->data_size()); diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 6147328404..06e1c65015 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -41,14 +41,29 @@ uint64_t Rescale(uint64_t time_in_old_scale, return (static_cast(time_in_old_scale) / old_scale) * new_scale; } +H26xStreamFormat GetH26xStreamFormat(FourCC fourcc) { + switch (fourcc) { + case FOURCC_avc1: + return H26xStreamFormat::kNalUnitStreamWithoutParameterSetNalus; + case FOURCC_avc3: + return H26xStreamFormat::kNalUnitStreamWithParameterSetNalus; + case FOURCC_hev1: + return H26xStreamFormat::kNalUnitStreamWithParameterSetNalus; + case FOURCC_hvc1: + return H26xStreamFormat::kNalUnitStreamWithoutParameterSetNalus; + default: + return H26xStreamFormat::kUnSpecified; + } +} + Codec FourCCToCodec(FourCC fourcc) { switch (fourcc) { case FOURCC_avc1: + case FOURCC_avc3: return kCodecH264; case FOURCC_hev1: - return kCodecHEV1; case FOURCC_hvc1: - return kCodecHVC1; + return kCodecH265; case FOURCC_vp08: return kCodecVP8; case FOURCC_vp09: @@ -495,7 +510,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { const FourCC actual_format = entry.GetActualFormat(); const Codec video_codec = FourCCToCodec(actual_format); switch (actual_format) { - case FOURCC_avc1: { + case FOURCC_avc1: + case FOURCC_avc3: { AVCDecoderConfigurationRecord avc_config; if (!avc_config.Parse(entry.codec_configuration.data)) { LOG(ERROR) << "Failed to parse avcc."; @@ -538,7 +554,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { LOG(ERROR) << "Failed to parse hevc."; return false; } - codec_string = hevc_config.GetCodecString(video_codec); + codec_string = hevc_config.GetCodecString(actual_format); nalu_length_size = hevc_config.nalu_length_size(); break; } @@ -564,7 +580,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted; scoped_refptr video_stream_info(new VideoStreamInfo( track->header.track_id, timescale, duration, video_codec, - codec_string, entry.codec_configuration.data.data(), + GetH26xStreamFormat(actual_format), codec_string, + entry.codec_configuration.data.data(), entry.codec_configuration.data.size(), coded_width, coded_height, pixel_width, pixel_height, 0, // trick_play_rate diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 58fbbdb554..cbd9ed5ffb 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -40,14 +40,18 @@ void SetStartAndEndFromOffsetAndSize(size_t offset, *end = *start + static_cast(size) - 1; } -FourCC CodecToFourCC(Codec codec) { +FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) { switch (codec) { case kCodecH264: - return FOURCC_avc1; - case kCodecHEV1: - return FOURCC_hev1; - case kCodecHVC1: - return FOURCC_hvc1; + return h26x_stream_format == + H26xStreamFormat::kNalUnitStreamWithParameterSetNalus + ? FOURCC_avc3 + : FOURCC_avc1; + case kCodecH265: + return h26x_stream_format == + H26xStreamFormat::kNalUnitStreamWithParameterSetNalus + ? FOURCC_hev1 + : FOURCC_hvc1; case kCodecVP8: return FOURCC_vp08; case kCodecVP9: @@ -93,8 +97,10 @@ Status MP4Muxer::Initialize() { ftyp->compatible_brands.push_back(FOURCC_mp41); if (streams().size() == 1 && streams()[0]->info()->stream_type() == kStreamVideo) { - const FourCC codec_fourcc = CodecToFourCC( - static_cast(streams()[0]->info().get())->codec()); + const FourCC codec_fourcc = + CodecToFourCC(streams()[0]->info()->codec(), + static_cast(streams()[0]->info().get()) + ->h26x_stream_format()); if (codec_fourcc != FOURCC_NULL) ftyp->compatible_brands.push_back(codec_fourcc); } @@ -220,7 +226,8 @@ void MP4Muxer::GenerateVideoTrak(const VideoStreamInfo* video_info, trak->header.height = video_info->height() * 0x10000; VideoSampleEntry video; - video.format = CodecToFourCC(video_info->codec()); + video.format = + CodecToFourCC(video_info->codec(), video_info->h26x_stream_format()); video.width = video_info->width(); video.height = video_info->height(); video.codec_configuration.data = video_info->codec_config(); @@ -243,7 +250,8 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info, trak->header.volume = 0x100; AudioSampleEntry audio; - audio.format = CodecToFourCC(audio_info->codec()); + audio.format = + CodecToFourCC(audio_info->codec(), H26xStreamFormat::kUnSpecified); switch(audio_info->codec()){ case kCodecAAC: audio.esds.es_descriptor.set_object_type(kISO_14496_3); // MPEG4 AAC. diff --git a/packager/media/formats/webm/segmenter_test_base.cc b/packager/media/formats/webm/segmenter_test_base.cc index c8573ada70..f5198248c8 100644 --- a/packager/media/formats/webm/segmenter_test_base.cc +++ b/packager/media/formats/webm/segmenter_test_base.cc @@ -87,9 +87,9 @@ MuxerOptions SegmentTestBase::CreateMuxerOptions() const { VideoStreamInfo* SegmentTestBase::CreateVideoStreamInfo() const { return new VideoStreamInfo(kTrackId, kTimeScale, kDuration, kCodec, - kCodecString, NULL, 0, kWidth, kHeight, - kPixelWidth, kPixelHeight, kTrickPlayRate, - kNaluLengthSize, kLanguage, false); + H26xStreamFormat::kUnSpecified, kCodecString, NULL, + 0, kWidth, kHeight, kPixelWidth, kPixelHeight, + kTrickPlayRate, kNaluLengthSize, kLanguage, false); } std::string SegmentTestBase::OutputFileName() const { diff --git a/packager/media/formats/webm/webm_cluster_parser_unittest.cc b/packager/media/formats/webm/webm_cluster_parser_unittest.cc index c145dfb5b4..1ef86b3d29 100644 --- a/packager/media/formats/webm/webm_cluster_parser_unittest.cc +++ b/packager/media/formats/webm/webm_cluster_parser_unittest.cc @@ -322,16 +322,38 @@ bool VerifyTextBuffers(const BlockInfo* block_info_ptr, class WebMClusterParserTest : public testing::Test { public: WebMClusterParserTest() - : audio_stream_info_(new AudioStreamInfo( - kAudioTrackNum, kTimeScale, kDuration, kUnknownCodec, kCodecString, - kExtraData, kExtraDataSize, kBitsPerSample, kNumChannels, - kSamplingFrequency, kSeekPreroll, kCodecDelay, 0, 0, kLanguage, - !kEncrypted)), - video_stream_info_(new VideoStreamInfo( - kVideoTrackNum, kTimeScale, kDuration, kCodecVP8, kCodecString, - kExtraData, kExtraDataSize, kWidth, kHeight, kPixelWidth, - kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, - !kEncrypted)), + : audio_stream_info_(new AudioStreamInfo(kAudioTrackNum, + kTimeScale, + kDuration, + kUnknownCodec, + kCodecString, + kExtraData, + kExtraDataSize, + kBitsPerSample, + kNumChannels, + kSamplingFrequency, + kSeekPreroll, + kCodecDelay, + 0, + 0, + kLanguage, + !kEncrypted)), + video_stream_info_(new VideoStreamInfo(kVideoTrackNum, + kTimeScale, + kDuration, + kCodecVP8, + H26xStreamFormat::kUnSpecified, + kCodecString, + kExtraData, + kExtraDataSize, + kWidth, + kHeight, + kPixelWidth, + kPixelHeight, + kTrickPlayRate, + kNaluLengthSize, + kLanguage, + !kEncrypted)), parser_(CreateDefaultParser()) {} protected: diff --git a/packager/media/formats/webm/webm_video_client.cc b/packager/media/formats/webm/webm_video_client.cc index 9e2f9fd06d..d6c6147424 100644 --- a/packager/media/formats/webm/webm_video_client.cc +++ b/packager/media/formats/webm/webm_video_client.cc @@ -109,9 +109,10 @@ scoped_refptr WebMVideoClient::GetVideoStreamInfo( sar_y /= gcd; return scoped_refptr(new VideoStreamInfo( - track_num, kWebMTimeScale, 0, video_codec, std::string(), - codec_private.data(), codec_private.size(), width_after_crop, - height_after_crop, sar_x, sar_y, 0, 0, std::string(), is_encrypted)); + track_num, kWebMTimeScale, 0, video_codec, H26xStreamFormat::kUnSpecified, + std::string(), codec_private.data(), codec_private.size(), + width_after_crop, height_after_crop, sar_x, sar_y, 0, 0, std::string(), + is_encrypted)); } bool WebMVideoClient::OnUInt(int id, int64_t val) { diff --git a/packager/media/formats/wvm/wvm_media_parser.cc b/packager/media/formats/wvm/wvm_media_parser.cc index 19206a87a3..aa4ca37693 100644 --- a/packager/media/formats/wvm/wvm_media_parser.cc +++ b/packager/media/formats/wvm/wvm_media_parser.cc @@ -108,8 +108,7 @@ WvmMediaParser::WvmMediaParser() media_sample_(NULL), crypto_unit_start_pos_(0), stream_id_count_(0), - decryption_key_source_(NULL) { -} + decryption_key_source_(NULL) {} WvmMediaParser::~WvmMediaParser() {} @@ -250,6 +249,7 @@ bool WvmMediaParser::Parse(const uint8_t* buf, int size) { if (HAS_HEADER_EXTENSION(pes_stream_id_)) { parse_state_ = PesExtension1; } else { + prev_pes_flags_1_ = pes_flags_1_; pes_flags_1_ = pes_flags_2_ = 0; pes_header_data_bytes_ = 0; parse_state_ = PesPayload; @@ -738,11 +738,11 @@ bool WvmMediaParser::ParseIndexEntry() { index_size = read_ptr - index_data_.data(); if (has_video) { - Codec video_codec = kCodecH264; - stream_infos_.push_back(new VideoStreamInfo( - stream_id_count_, time_scale, track_duration, video_codec, - std::string(), video_codec_config.data(), video_codec_config.size(), - video_width, video_height, pixel_width, pixel_height, trick_play_rate, + stream_infos_.emplace_back(new VideoStreamInfo( + stream_id_count_, time_scale, track_duration, kCodecH264, + byte_to_unit_stream_converter_.stream_format(), std::string(), + video_codec_config.data(), video_codec_config.size(), video_width, + video_height, pixel_width, pixel_height, trick_play_rate, nalu_length_size, std::string(), true)); program_demux_stream_map_[base::UintToString(index_program_id_) + ":" + base::UintToString( diff --git a/packager/media/test/data/avc-unit-stream-frame.h264 b/packager/media/test/data/avc1-unit-stream-frame.h264 similarity index 100% rename from packager/media/test/data/avc-unit-stream-frame.h264 rename to packager/media/test/data/avc1-unit-stream-frame.h264 diff --git a/packager/media/test/data/avc3-unit-stream-frame.h264 b/packager/media/test/data/avc3-unit-stream-frame.h264 new file mode 100644 index 0000000000000000000000000000000000000000..357edaf3171a5398fc2262da66a74af7582725a8 GIT binary patch literal 5653 zcmai22UJs8x4t15LJdWlKmh520#Zdn02QT+im2345hDf!=_Lt*B1J(^5mAZ-1f{4D z=?P^36%0sITB0Z|0s+B<5OQB|=FNI<{LNUKo|xHqziu` z0Dv{m0BP73325?)OOKG_SkY~Zwt+lZWnlAxl$Nu#XsMjGm7k~$!KS>bJeCUK-R9Sx z5WEfm@CJ=q6_qm_oSxZMz|S%%i)@*ck{?Bm7Stf94Ud|l1OKEg@xDnJD#bq?Lmb<) z58NzcFI8 z7f*d|-D9vbNaj7MaH~={gNt|YdHpaOtUR)J8k3RH!`+UMPo6q_&qEQEuVvb}HEDdi8KPtcRNupZ~$lFK`x-xik6%B_po8ME}jM zmC!|4iT?Ff%Vog-S&;Wd@mQA6xH8pvw#jp`Q-B>_FSk}x#Pl6~{a|)I-g#_6cgA5A zLewTlOE~hK`#gVp2qA~qt`iK&oB8H#l}fKIndVOV(vFlxmQv?S7RSSv(uOZi>f(Zy z!ut2-Vw(gu*h`}4bcKU>1(R@;&qZAvt{k0C7r3?c%~D5^Hoj)kI_TD}N;5wDXo zu#c0HWT#!LugK4nR* zA$I|j<4zOxPgYG|Z7hoL@N?(E0!I9v9Z#QcEP-em+t3-aw8mh2F6q4_EbVrT=S8n+ zk4=A(ngv!)r>#x?Kw*iW6o%T>qC*%KW;x2BGC}qC9dej|4#Q&idyvI}2xE!l2_p2d z9x0*kt}ob0gL`6$u+=Ihd4G;r`YVu&25d*MRc7UcnfLlM9uQn&@yU#06Phjx*qy|0 z0>KQjIC1o%DBX|8$>IS)4i30V9Q6zKgzz}YJRms9;NM9cg~oj1adJ0-pt3b|e644l zYw-6>WZNH=hE$GN%EF~rJlCS;a*E`QIY>xOP zL1}?5z5cNRh7}o#B+Y@FADY0a0Z}{ZMx5#qEUKNCjZ;7yqVXwdjW@@~Qg0?To=H_Q z;M>~o44L{#3p-H971-}t&`SMCOj8s{$u-Indx83#-9nDa%{IqrAi~7Y{3yPzd$us| zR)h<>0_}QbWIiod;)Z*-Tuktbt2AL>7r}G!0TELhkck(nM}f3uXVnYgMDL5MIz!!F zkic%!z3`qIq|wxO4_`@DM1$M>Wm!#(4eXodEz7+tInAGU;m+Eb_&A|V)*d#w#O{K~+AMpRj?HV_%=v~f zX%!pmcqA-+XP{@}Er|bhX{+B;_sn-)k4NU>WPr6-I_1bv72rqa^gnOtX|EfU^17f=^6y#BMC$ zfKjd@hQ{ZEI|xG#Lp~z!vf#6`tnn-(UsP``Rcu2sDQGGMnAa%QS@}>jmy5>B4p2A( zhcJ5eKOp#5-@Jst_F344(_6y0mj)HoCN24+e=h5nCMjNZQ!7`x__Wxy3!3lZr#KgJ zuLAQRuJ@IT5jpHy*2EV4m$XdXqCEAOQ`wniQQ<3X19GojKthHUE+pP*m;m+R6)iNg%>->{++K%C+MSq;gPt0MD|whAVjlGJR!!y7dZRXZ_G-j54tF zgCGde9l%}QLh>l_Bxo>gz}TUj8N?E<6$(-q*1lJi6V^XGy?GBJgefNcjAmGD9|NI} zc2d}`Tn!;}gw%F_@`>Y)Wz#pI?OnO8{MWTWLEGaGuudUwv+$3{Qau;?Va@Z9n)LFu zz*Hyn7o8E}V)YF( z%%)e6=7W=pY{Z4CX;y{%zni|ul!%X10+;tMyI5HR&c#_+^?p2Oo6GcA4d@W3Dg-C% zKZUIFY23a4%Gv%gO%jm;Yz^i!3jUOt#_C})K> zFD}yvf~~2LpQ;>=gy~qHoT7pK==ptk%~1`D%3pZI<8ucTk44;>TTAJE5*i_;LPHA6 zkP{x7yeLDB-IeQvZvB3}{EZoe4Ojne?$9e&qPK4XSz-OFUob}Wn(JzYrdP_;Y-@fN zX1=8;Oz|cwqQm@$o%TMbdiU2LI0E+qQoGoaH*YkALgyakTln$o6yJyTOp|BQE_1}L zzcK&u`(^PnO2E#ufcN?s154=@^}N9V08wEL$SxWokpwbU4g>m=Q-DbM_&6Tq0`uii z6Elz+4g29CmxVm zH4=4LSQ8Y6?Idf*CSToM7oBm{mn(kTgKyXDG@XP7!PCF)8f0#fc>>p#HrO5&u1^z* zTUJvBM5zZUNCXJB06DxUZ0}>p|Klg;6*t5hLXNtgk~>LQl8{1!9L>nZ&8FHM{d)>1 zsS>w)m6Jg}%}>fr521{Yh&I-SZOoSw&I?dZ35(BUjuJY%Ct9?97-VkO^ScQIP=_MO zBJ1oAt@yshUiiGG04w_!q7@0O8%l(N2m|$)iC^o7l_yJBXu*Xj+ke;8s;VhZ`K5SI z!-B=P+3yMIVf&=lT1-XUr;^>d>~f0T0jW2Po}W&C>?|4{wh{BNRUu`+d}cvP>1o$I6p^ z_<>z}PahleXKxJOnsD`jM;{g$CSDP7G!!06|3Q|@I?SA&d%~~gq70xmgc}A+NwnC? z?$U;n#N@tw0$a+h`fgQYu6$)QzeICx3i_1x%r6dqM0qfa&n!u2;$|r+u3ZV|?>y2& zv`SwQ=zV(NAnCHEUEqWbe{k_>*P&Dk3zo!4GKHoVW&3b$fVy_woHLSFlXo#*eg;wG zkeY#;a!Ma=yaIn)+pTJX-5Tc3P^&={kX4jVBUfVb7(K*V1^#H(!VtD-HBRZXOc_Yo z4>g%Dw2?QHzwYd57o#DV8RAwP7bp1c<>I*M$k&TPmYm2y(@$(%as5uV`_0nDAdQR>5dC(>I@TLyXN z6lsg0G;$*_!q)YVga}67wn}^Ekonap=HlAwiVz$UX&7uC7WAa!2)*x;7GB*#PBtLv zlliKEW-C6w$K;&?K7w2%<=4pX2{_(r^EGchDtS*Y`jPdxhGdXIWGe^1*GG~u{~PQy5! zJI)Uc5?=O>GPDl$bTY(_r+AP(Px0@JpteAaPOSD`+SPgB=sbQsOk9*k(imPjta!Iy z#53}=J40sZip(3Iasw%o_l;s(PRtbJ1HoV}!#8WjKR>O?kYyzK>SLkp5Ph&%;?EJm z6Day4OrRn2=P1~WGVNFQLPc|-%JbbCF_Z5EG5PRcsBcPXHGRf4_tw|3AQhR1xh)AQ z(+s&|tsWJY298b)wFwl>vF&r%{tOp3m&#&x7LWMzlR8-+t3Ukx_}jKEy>eP7+Fspe z#qP|6CTB>KHBYe@E=eepJy;(T?hNWWF|z0Vt?MCh<1^IfArbX)mQfWz`*SZ17XLwp zG*{$|ZrSnNFIpq-62kbz<+sMJ2JIT^g0yiwfZRZO9eF<<;ydX8^Eg&+)aqK38Ck6g zOOIXE>yCXlg12Ww7P2cw}{8 z+vnqMd$F(fftMtt!-p4XPI>pTAX+a(uaZCB+f$v#Q3@!=Cq<#=BvEqgJ?h5XIrV-^ zshOA+{``9i!i3X_`Pm^;3$f4JJ_nKhax+{BJ%rg9urH3%>Rp`?L>ZXMLIYbU_9Rg} zYVgaCdVT&F``3u5U!$rO%PgWzWDPhQG^* zzFedoGSe9Tx=2Lsu)n5xX~u(c#dUs*OyY-yTJyiWL>{#eQ5^oncqGUBSKFWV2JIg# zb*EdjeqBkknU3`GY#~3FQ=J&FYHLQZ+7m;)qjnTrK;F#;P6ya^aJrCJ6vtbHf81?A za&w2wrO!YmG53Q&M2b1xG!uq(+E_(boOMdc_s1-(zj#~robvOxYBBs}bw|%V?-17c z*(ujXP^7uObE-AVdm7y6j|9JH(@k$F96g@TPUC_%ZzxpO!?1&nH6U~xWI0&$yj3`A z8?=uO=HKW57xf%lSR9+uHJYlzx5FH^tkwj+^VA}{L)3jE7-S(A!Pdh`!M6_Kux6^O zVEW?_()xQz1`aKL0T%_gTic4Zy84z`ur$=beVOQv_vFdQzA_w4g}OcYOL?8uPi`#z z^)ES-V>l|v^rnJIMR2Nkv2wcTP$UJ!-B4(rf2l7p$w)lLfxvl?siM&MX-;?rSS~l{ zI|PCW3m`{B`bgGuGKHzLYZ0W_O-3@4Q=-ty_s)w;N^4BOE_2hANt5M~8|OR9xNkrX zVl(-&+jo3Kz)%l-@cIg-Vd2xZyY#=Pc1uh0sJKUXk?g+Fu}JD~Z&c2pUBJcuam1Sz zfS!4|&UrVCA4QSQ!NSXWM9kZa>##h9FB z$MQC|G5;GOYIp!W4U%W_r$MkilXrF`q3O5BG#i@GZo}K#ccmC{R2fNb&}+MaFX@S7 zH~TP^rV;Uj(w9|`m5S~igqZqN-CSUKj`Ig7WG!7k0-1}FR%<3&qkN9f79uC#jT=M9 z;tm4I}$Og*p67p{J7dTAg_p@6bnQTxzd zwT_*U-OSr<{~j6tAw`xETn(fcw7okwfq~I`oI??Sd_CvXx_pIrul0{ux!xT52?GDg zs$>MH<8XFL4VV#%7?k!j^ zOLs*JO?QGV{#J5Z9TPP!>*y8~pQ2aP&JrNT*%;GoES>{gbX2F9fHCQac(Ve~(<%C7 z6(l9JX_jPb-`7DqA=1z+LG6`zQ}-m+qw_W$%^bbf53b?4X71ijCjvF}<0rXze!(Yi za~IYz{>Sr3Ck(t4^%EuMKxg*0m*)t>AK#fU6n>ZwKWTue1fVFT%(oK>h$SjXzh`|J z4aQx|M~W7?THn918IySN{qqR3f=ylbTi^lU*FGA}(&=q3jRqB8)=noR(=4>?FUI)6 zk7A5rt8aKm8Ygi)i&_P2^Xv=F%1J!j`%UY7#m;M#XKe6EW2L8(EpR9jCgs_@~3F1E> hrT;(Na6m?kI=_yfH4E%#S_$l5W+_RHK_5lO{TB!DS4;o^ literal 0 HcmV?d00001 diff --git a/packager/media/test/data/hev1-unit-stream-frame.h265 b/packager/media/test/data/hev1-unit-stream-frame.h265 new file mode 100644 index 0000000000000000000000000000000000000000..fdcb347edd6ae231589ff34d188b35bb06951a7e GIT binary patch literal 11403 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 dP0han%XkO?aEBG#2tYgmycz