From 277c5b23c01c8992e26ad12e11266043dead5c12 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 + .../media/base/media_handler_test_base.cc | 7 +-- 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 | 24 +++++++++ .../hevc_decoder_configuration_record.cc | 17 ++----- .../hevc_decoder_configuration_record.h | 3 +- ...c_decoder_configuration_record_unittest.cc | 4 +- packager/media/crypto/encryption_handler.cc | 7 +-- .../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 | 22 +++++--- .../media/formats/mp2t/ts_writer_unittest.cc | 31 +++++++---- packager/media/formats/mp4/box_definitions.cc | 2 + .../media/formats/mp4/mp4_media_parser.cc | 27 ++++++++-- packager/media/formats/mp4/mp4_muxer.cc | 25 +++++---- .../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 | 12 ++--- ...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 35 files changed, 358 insertions(+), 135 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 a1554d490e..153949739d 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/media_handler_test_base.cc b/packager/media/base/media_handler_test_base.cc index d5c38316b6..c1b336441e 100644 --- a/packager/media/base/media_handler_test_base.cc +++ b/packager/media/base/media_handler_test_base.cc @@ -159,9 +159,10 @@ std::shared_ptr MediaHandlerTestBase::GetMockStreamInfo( !kEncrypted)); } else if (codec >= kCodecVideo && codec < kCodecVideoMaxPlusOne) { return std::shared_ptr(new VideoStreamInfo( - kTrackId, time_scale, kDuration, codec, kCodecString, kCodecConfig, - sizeof(kCodecConfig), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayRate, kNaluLengthSize, kLanguage, !kEncrypted)); + kTrackId, time_scale, kDuration, codec, H26xStreamFormat::kUnSpecified, + kCodecString, kCodecConfig, sizeof(kCodecConfig), kWidth, kHeight, + kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, + !kEncrypted)); } return nullptr; } diff --git a/packager/media/base/stream_info.h b/packager/media/base/stream_info.h index f1cbf656f9..538f5e5248 100644 --- a/packager/media/base/stream_info.h +++ b/packager/media/base/stream_info.h @@ -27,12 +27,7 @@ enum Codec { kCodecVideo = 100, kCodecH264 = kCodecVideo, - kCodecHEV1, - kCodecHVC1, - kCodecVC1, - kCodecMPEG2, - kCodecMPEG4, - kCodecTheora, + kCodecH265, kCodecVP8, kCodecVP9, kCodecVP10, @@ -41,6 +36,8 @@ enum Codec { kCodecAudio = 200, kCodecAAC = kCodecAudio, 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 9372518263..d5e26898b6 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); ~VideoStreamInfo() override; @@ -34,6 +50,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: + 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 7c98c6c35a..bb4bc94275 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 constexpr size_t kUnitStreamNaluLengthSize = 4; + /// 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/crypto/encryption_handler.cc b/packager/media/crypto/encryption_handler.cc index 4ae87aa79c..da4ffd9ff7 100644 --- a/packager/media/crypto/encryption_handler.cc +++ b/packager/media/crypto/encryption_handler.cc @@ -151,9 +151,7 @@ Status EncryptionHandler::ProcessStreamInfo(StreamInfo* stream_info) { case kCodecH264: header_parser_.reset(new H264VideoSliceHeaderParser); break; - case kCodecHVC1: - FALLTHROUGH_INTENDED; - case kCodecHEV1: + case kCodecH265: header_parser_.reset(new H265VideoSliceHeaderParser); break; default: @@ -436,8 +434,7 @@ bool EncryptionHandler::EncryptNalFrame(MediaSample* sample, DCHECK_NE(nalu_length_size_, 0u); DCHECK(header_parser_); const Nalu::CodecType nalu_type = - (codec_ == kCodecHVC1 || codec_ == kCodecHEV1) ? Nalu::kH265 - : Nalu::kH264; + (codec_ == kCodecH265) ? Nalu::kH265 : Nalu::kH264; NaluReader reader(nalu_type, nalu_length_size_, sample->writable_data(), sample->data_size()); diff --git a/packager/media/event/muxer_listener_test_helper.cc b/packager/media/event/muxer_listener_test_helper.cc index ff23526a26..718b1feb4c 100644 --- a/packager/media/event/muxer_listener_test_helper.cc +++ b/packager/media/event/muxer_listener_test_helper.cc @@ -18,8 +18,9 @@ std::shared_ptr CreateVideoStreamInfo( const VideoStreamInfoParameters& param) { return std::make_shared( 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 c180480920..b71b958e9c 100644 --- a/packager/media/formats/mp2t/es_parser_h264.cc +++ b/packager/media/formats/mp2t/es_parser_h264.cc @@ -150,6 +150,7 @@ bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) { H26xByteToUnitStreamConverter::kUnitStreamNaluLengthSize; last_video_decoder_config_ = std::make_shared( 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 31e0cb08ff..6da887302d 100644 --- a/packager/media/formats/mp2t/es_parser_h265.cc +++ b/packager/media/formats/mp2t/es_parser_h265.cc @@ -153,9 +153,14 @@ bool EsParserH265::UpdateVideoDecoderConfig(int pps_id) { const uint8_t nalu_length_size = H26xByteToUnitStreamConverter::kUnitStreamNaluLengthSize; + const H26xStreamFormat stream_format = stream_converter()->stream_format(); + const FourCC codec_fourcc = + stream_format == H26xStreamFormat::kNalUnitStreamWithParameterSetNalus + ? FOURCC_hev1 + : FOURCC_hvc1; last_video_decoder_config_ = std::make_shared( - 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, nalu_length_size, 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 6c02b86532..6b2d458577 100644 --- a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc +++ b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc @@ -115,7 +115,8 @@ class MockAACAudioSpecificConfig : public AACAudioSpecificConfig { std::shared_ptr CreateVideoStreamInfo(Codec codec) { std::shared_ptr 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; @@ -344,9 +345,10 @@ TEST_F(PesPacketGeneratorTest, AddAudioSampleFailedToConvert) { TEST_F(PesPacketGeneratorTest, TimeStampScaling) { const uint32_t kTestTimescale = 1000; std::shared_ptr 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 21c134f667..f128fdfcde 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -97,7 +97,8 @@ class TsSegmenterTest : public ::testing::Test { TEST_F(TsSegmenterTest, Initialize) { std::shared_ptr 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; @@ -117,7 +118,8 @@ TEST_F(TsSegmenterTest, Initialize) { TEST_F(TsSegmenterTest, AddSample) { std::shared_ptr 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; @@ -168,9 +170,10 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // done correctly in the segmenter. const uint32_t kInputTimescale = 1001; std::shared_ptr 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_template = "file$Number$.ts"; @@ -262,7 +265,8 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // Finalize right after Initialize(). The writer will not be initialized. TEST_F(TsSegmenterTest, InitializeThenFinalize) { std::shared_ptr 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; @@ -290,7 +294,8 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { // writer with a mock. TEST_F(TsSegmenterTest, FinalizeSegment) { std::shared_ptr 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; @@ -317,7 +322,8 @@ TEST_F(TsSegmenterTest, FinalizeSegment) { TEST_F(TsSegmenterTest, EncryptedSample) { std::shared_ptr 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 bb90dfb72c..ff5672adca 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) { std::shared_ptr 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) { std::shared_ptr 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()); std::shared_ptr 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()); std::shared_ptr 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()); std::shared_ptr 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) { std::shared_ptr 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) { std::shared_ptr 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) { std::shared_ptr 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) { std::shared_ptr 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 edd5438a83..37a1eb6e95 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/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 005a03a3fc..b46d4fd1b9 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -40,14 +40,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: @@ -497,7 +512,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."; @@ -540,7 +556,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; } @@ -569,7 +585,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted; std::shared_ptr 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 00e011e791..8b90fb4d90 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -39,14 +39,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: @@ -115,7 +119,8 @@ Status MP4Muxer::InitializeMuxer() { ftyp->compatible_brands.push_back(FOURCC_mp41); if (streams().size() == 1 && streams()[0]->stream_type() == kStreamVideo) { const FourCC codec_fourcc = CodecToFourCC( - static_cast(streams()[0].get())->codec()); + streams()[0]->codec(), static_cast(streams()[0].get()) + ->h26x_stream_format()); if (codec_fourcc != FOURCC_NULL) ftyp->compatible_brands.push_back(codec_fourcc); } @@ -250,7 +255,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(); @@ -282,7 +288,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 3452ffc4a4..ac876fb83f 100644 --- a/packager/media/formats/webm/segmenter_test_base.cc +++ b/packager/media/formats/webm/segmenter_test_base.cc @@ -83,9 +83,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 77ea0f1fdf..800e34a9ec 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 b941e89b66..e742951d6c 100644 --- a/packager/media/formats/webm/webm_video_client.cc +++ b/packager/media/formats/webm/webm_video_client.cc @@ -109,9 +109,10 @@ std::shared_ptr WebMVideoClient::GetVideoStreamInfo( sar_y /= gcd; return std::make_shared( - 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 31d58b8f10..0b090fe7c1 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; @@ -739,11 +739,11 @@ bool WvmMediaParser::ParseIndexEntry() { index_size = read_ptr - index_data_.data(); if (has_video) { - Codec video_codec = kCodecH264; stream_infos_.emplace_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_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(), decryption_key_source_ ? false : true)); program_demux_stream_map_[base::UintToString(index_program_id_) + ":" + 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 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 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