diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index a215ea44f3..6dc4e7f3bc 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -1203,6 +1203,15 @@ class PackagerFunctionalTest(PackagerAppTest): self.assertPackageSuccess(streams, flags) self._CheckTestResults('hevc-with-encryption', verify_decryption=True) + def testHdr10WithEncryption(self): + streams = [ + self._GetStream('video', test_file='bear-640x360-hevc-hdr10.mp4') + ] + flags = self._GetFlags(encryption=True, output_dash=True, output_hls=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('hdr10-with-encryption') + def testDolbyVisionWithEncryption(self): streams = [ self._GetStream('video', test_file='426x240-dvh1.mp4') diff --git a/packager/app/test/testdata/hdr10-with-encryption/bear-640x360-hevc-hdr10-video.mp4 b/packager/app/test/testdata/hdr10-with-encryption/bear-640x360-hevc-hdr10-video.mp4 new file mode 100644 index 0000000000..63e0042f29 Binary files /dev/null and b/packager/app/test/testdata/hdr10-with-encryption/bear-640x360-hevc-hdr10-video.mp4 differ diff --git a/packager/app/test/testdata/hdr10-with-encryption/output.m3u8 b/packager/app/test/testdata/hdr10-with-encryption/output.m3u8 new file mode 100644 index 0000000000..172e574684 --- /dev/null +++ b/packager/app/test/testdata/hdr10-with-encryption/output.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-STREAM-INF:BANDWIDTH=317223,AVERAGE-BANDWIDTH=317223,CODECS="hvc1.2.4.L63.90",RESOLUTION=640x360,VIDEO-RANGE=PQ +stream_0.m3u8 diff --git a/packager/app/test/testdata/hdr10-with-encryption/output.mpd b/packager/app/test/testdata/hdr10-with-encryption/output.mpd new file mode 100644 index 0000000000..4a76f34309 --- /dev/null +++ b/packager/app/test/testdata/hdr10-with-encryption/output.mpd @@ -0,0 +1,18 @@ + + + + + + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== + + + bear-640x360-hevc-hdr10-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/hdr10-with-encryption/stream_0.m3u8 b/packager/app/test/testdata/hdr10-with-encryption/stream_0.m3u8 new file mode 100644 index 0000000000..63762cbc6b --- /dev/null +++ b/packager/app/test/testdata/hdr10-with-encryption/stream_0.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-640x360-hevc-hdr10-video.mp4",BYTERANGE="5227@0" +#EXTINF:2.803, +#EXT-X-BYTERANGE:111139@5271 +bear-640x360-hevc-hdr10-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index b885066617..8856007e37 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -222,6 +222,9 @@ void BuildStreamInfTag(const MediaPlaylist& playlist, uint32_t height; if (playlist.GetDisplayResolution(&width, &height)) { tag.AddNumberPair("RESOLUTION", width, 'x', height); + const std::string video_range = playlist.GetVideoRange(); + if (!video_range.empty()) + tag.AddString("VIDEO-RANGE", video_range); } if (variant.audio_group_id) { diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 7544cca7d6..2b3166794d 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -527,6 +527,22 @@ bool MediaPlaylist::GetDisplayResolution(uint32_t* width, return false; } +std::string MediaPlaylist::GetVideoRange() const { + // HLS specification: + // https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-02#section-4.4.4.2 + switch (media_info_.video_info().transfer_characteristics()) { + case 1: + return "SDR"; + case 16: + case 18: + return "PQ"; + default: + // Leave it empty if we do not have the transfer characteristics + // information. + return ""; + } +} + void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name, int64_t start_time, int64_t duration, diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index b0a0823f8e..fef00baad0 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -186,6 +186,9 @@ class MediaPlaylist { /// resolution values. virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const; + /// @return The video range of the stream. + virtual std::string GetVideoRange() const; + /// @return the language of the media, as an ISO language tag in its shortest /// form. May be an empty string for video. const std::string& language() const { return language_; } diff --git a/packager/media/base/media_handler_test_base.cc b/packager/media/base/media_handler_test_base.cc index 69444c9d87..87e42aa400 100644 --- a/packager/media/base/media_handler_test_base.cc +++ b/packager/media/base/media_handler_test_base.cc @@ -28,6 +28,7 @@ const uint16_t kWidth = 10u; const uint16_t kHeight = 20u; const uint32_t kPixelWidth = 2u; const uint32_t kPixelHeight = 3u; +const uint8_t kTransferCharacteristics = 0; const int16_t kTrickPlayFactor = 0; const uint8_t kNaluLengthSize = 1u; const bool kEncrypted = true; @@ -199,8 +200,8 @@ std::unique_ptr MediaHandlerTestBase::GetVideoStreamInfo( return std::unique_ptr(new VideoStreamInfo( kTrackId, time_scale, kDuration, codec, H26xStreamFormat::kUnSpecified, kCodecString, kCodecConfig, sizeof(kCodecConfig), width, height, - kPixelWidth, kPixelHeight, kTrickPlayFactor, kNaluLengthSize, kLanguage, - !kEncrypted)); + kPixelWidth, kPixelHeight, kTransferCharacteristics, kTrickPlayFactor, + kNaluLengthSize, kLanguage, !kEncrypted)); } std::unique_ptr MediaHandlerTestBase::GetAudioStreamInfo( diff --git a/packager/media/base/video_stream_info.cc b/packager/media/base/video_stream_info.cc index 1e58f0f578..d927f6a1ff 100644 --- a/packager/media/base/video_stream_info.cc +++ b/packager/media/base/video_stream_info.cc @@ -50,6 +50,7 @@ VideoStreamInfo::VideoStreamInfo(int track_id, uint16_t height, uint32_t pixel_width, uint32_t pixel_height, + uint8_t transfer_characteristics, uint32_t trick_play_factor, uint8_t nalu_length_size, const std::string& language, @@ -69,6 +70,7 @@ VideoStreamInfo::VideoStreamInfo(int track_id, height_(height), pixel_width_(pixel_width), pixel_height_(pixel_height), + transfer_characteristics_(transfer_characteristics), trick_play_factor_(trick_play_factor), nalu_length_size_(nalu_length_size) {} diff --git a/packager/media/base/video_stream_info.h b/packager/media/base/video_stream_info.h index 5c6a49a063..76d518fc8d 100644 --- a/packager/media/base/video_stream_info.h +++ b/packager/media/base/video_stream_info.h @@ -39,6 +39,7 @@ class VideoStreamInfo : public StreamInfo { uint16_t height, uint32_t pixel_width, uint32_t pixel_height, + uint8_t transfer_characteristics, uint32_t trick_play_factor, uint8_t nalu_length_size, const std::string& language, @@ -63,6 +64,7 @@ class VideoStreamInfo : public StreamInfo { /// Returns the pixel height. /// @return 0 if unknown. uint32_t pixel_height() const { return pixel_height_; } + uint8_t transfer_characteristics() const { return transfer_characteristics_; } uint8_t nalu_length_size() const { return nalu_length_size_; } uint32_t trick_play_factor() const { return trick_play_factor_; } uint32_t playback_rate() const { return playback_rate_; } @@ -75,6 +77,9 @@ class VideoStreamInfo : public StreamInfo { void set_height(uint32_t height) { height_ = height; } void set_pixel_width(uint32_t pixel_width) { pixel_width_ = pixel_width; } void set_pixel_height(uint32_t pixel_height) { pixel_height_ = pixel_height; } + void set_transfer_characteristics(uint8_t transfer_characteristics) { + transfer_characteristics_ = transfer_characteristics; + } void set_trick_play_factor(uint32_t trick_play_factor) { trick_play_factor_ = trick_play_factor; } @@ -98,6 +103,7 @@ class VideoStreamInfo : public StreamInfo { // 0 means unknown. uint32_t pixel_width_; uint32_t pixel_height_; + uint8_t transfer_characteristics_ = 0; uint32_t trick_play_factor_ = 0; // Non-zero for trick-play streams. // Playback rate is the attribute for trick play stream, which signals the diff --git a/packager/media/codecs/avc_decoder_configuration_record.cc b/packager/media/codecs/avc_decoder_configuration_record.cc index 580c0b9638..1a0d7b9a66 100644 --- a/packager/media/codecs/avc_decoder_configuration_record.cc +++ b/packager/media/codecs/avc_decoder_configuration_record.cc @@ -64,6 +64,8 @@ bool AVCDecoderConfigurationRecord::ParseInternal() { int sps_id = 0; H264Parser parser; RCHECK(parser.ParseSps(nalu, &sps_id) == H264Parser::kOk); + set_transfer_characteristics( + parser.GetSps(sps_id)->transfer_characteristics); RCHECK(ExtractResolutionFromSps(*parser.GetSps(sps_id), &coded_width_, &coded_height_, &pixel_width_, &pixel_height_)); diff --git a/packager/media/codecs/avc_decoder_configuration_record_unittest.cc b/packager/media/codecs/avc_decoder_configuration_record_unittest.cc index 4816aad618..635353b302 100644 --- a/packager/media/codecs/avc_decoder_configuration_record_unittest.cc +++ b/packager/media/codecs/avc_decoder_configuration_record_unittest.cc @@ -12,11 +12,24 @@ namespace shaka { namespace media { TEST(AVCDecoderConfigurationRecordTest, Success) { + // clang-format off const uint8_t kAvcDecoderConfigurationData[] = { - 0x01, 0x64, 0x00, 0x1E, 0xFF, 0xE1, 0x00, 0x1D, 0x67, 0x64, 0x00, 0x1E, - 0xAC, 0xD9, 0x40, 0xB4, 0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91, - 0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA, 0x60, 0x0F, 0x16, 0x2D, - 0x96, 0x01, 0x00, 0x06, 0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0}; + 0x01, // version + 0x64, // profile_indication + 0x00, // profile_compatibility + 0x1E, // avc_level + 0xFF, // Least significant 3 bits is length_size_minus_one + 0xE1, // Least significant 5 bits is num_sps + // sps 1 + 0x00, 0x1D, // size + 0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4, 0x2F, 0xF9, 0x7F, 0xF0, + 0x00, 0x80, 0x00, 0x91, 0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA, + 0x60, 0x0F, 0x16, 0x2D, 0x96, + 0x01, // num_pps + 0x00, 0x06, // size + 0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0, + }; + // clang-format on AVCDecoderConfigurationRecord avc_config; ASSERT_TRUE(avc_config.Parse(kAvcDecoderConfigurationData, @@ -31,17 +44,71 @@ TEST(AVCDecoderConfigurationRecordTest, Success) { EXPECT_EQ(360u, avc_config.coded_height()); EXPECT_EQ(8u, avc_config.pixel_width()); EXPECT_EQ(9u, avc_config.pixel_height()); + EXPECT_EQ(0u, avc_config.transfer_characteristics()); + + EXPECT_EQ("avc1.64001e", avc_config.GetCodecString(FOURCC_avc1)); + EXPECT_EQ("avc3.64001e", avc_config.GetCodecString(FOURCC_avc3)); +} + +TEST(AVCDecoderConfigurationRecordTest, SuccessWithTransferCharacteristics) { + // clang-format off + const uint8_t kAvcDecoderConfigurationData[] = { + 0x01, // version + 0x64, // profile_indication + 0x00, // profile_compatibility + 0x1E, // avc_level + 0xFF, // Least significant 3 bits is length_size_minus_one + 0xE1, // Least significant 5 bits is num_sps + // sps 1 + 0x00, 0x22, // size + 0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4, 0x2F, 0xF9, 0x7F, 0xF0, + 0x00, 0x80, 0x00, 0x96, 0xA1, 0x22, 0x01, 0x28, 0x00, 0x00, 0x03, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x01, 0x80, 0x78, 0xB1, 0x6C, 0xB0, + 0x01, // num_pps + // pps 1 + 0x00, 0x06, // size + 0x68, 0xEB, 0xE1, 0x32, 0xC8, 0xB0, + }; + // clang-format on + + AVCDecoderConfigurationRecord avc_config; + ASSERT_TRUE(avc_config.Parse(kAvcDecoderConfigurationData, + arraysize(kAvcDecoderConfigurationData))); + + EXPECT_EQ(1u, avc_config.version()); + EXPECT_EQ(0x64, avc_config.profile_indication()); + EXPECT_EQ(0u, avc_config.profile_compatibility()); + EXPECT_EQ(0x1E, avc_config.avc_level()); + EXPECT_EQ(4u, avc_config.nalu_length_size()); + EXPECT_EQ(720u, avc_config.coded_width()); + EXPECT_EQ(360u, avc_config.coded_height()); + EXPECT_EQ(8u, avc_config.pixel_width()); + EXPECT_EQ(9u, avc_config.pixel_height()); + EXPECT_EQ(16u, avc_config.transfer_characteristics()); EXPECT_EQ("avc1.64001e", avc_config.GetCodecString(FOURCC_avc1)); EXPECT_EQ("avc3.64001e", avc_config.GetCodecString(FOURCC_avc3)); } TEST(AVCDecoderConfigurationRecordTest, FailsOnInvalidNaluLengthSize) { + // clang-format off const uint8_t kAvcDecoderConfigurationData[] = { - 0x01, 0x64, 0x00, 0x1E, 0xFE, 0xE1, 0x00, 0x1D, 0x67, 0x64, 0x00, 0x1E, - 0xAC, 0xD9, 0x40, 0xB4, 0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91, - 0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA, 0x60, 0x0F, 0x16, 0x2D, - 0x96, 0x01, 0x00, 0x06, 0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0}; + 0x01, // version + 0x64, // profile_indication + 0x00, // profile_compatibility + 0x1E, // avc_level + 0xFE, // Least significant 3 bits is length_size_minus_one + 0xE1, // Least significant 5 bits is num_sps + // sps 1 + 0x00, 0x1D, // size + 0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4, 0x2F, 0xF9, 0x7F, 0xF0, + 0x00, 0x80, 0x00, 0x91, 0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA, + 0x60, 0x0F, 0x16, 0x2D, 0x96, + 0x01, // num_pps + 0x00, 0x06, // size + 0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0, + }; + // clang-format on AVCDecoderConfigurationRecord avc_config; ASSERT_FALSE(avc_config.Parse(kAvcDecoderConfigurationData, diff --git a/packager/media/codecs/decoder_configuration_record.cc b/packager/media/codecs/decoder_configuration_record.cc index 7e437c26d5..ff99b1790b 100644 --- a/packager/media/codecs/decoder_configuration_record.cc +++ b/packager/media/codecs/decoder_configuration_record.cc @@ -9,9 +9,8 @@ namespace shaka { namespace media { -DecoderConfigurationRecord::DecoderConfigurationRecord() - : nalu_length_size_(0) {} -DecoderConfigurationRecord::~DecoderConfigurationRecord() {} +DecoderConfigurationRecord::DecoderConfigurationRecord() = default; +DecoderConfigurationRecord::~DecoderConfigurationRecord() = default; bool DecoderConfigurationRecord::Parse(const uint8_t* data, size_t data_size) { data_.assign(data, data + data_size); diff --git a/packager/media/codecs/decoder_configuration_record.h b/packager/media/codecs/decoder_configuration_record.h index 7d1018b1fd..cb06d966ab 100644 --- a/packager/media/codecs/decoder_configuration_record.h +++ b/packager/media/codecs/decoder_configuration_record.h @@ -43,6 +43,9 @@ class DecoderConfigurationRecord { /// lifetime of this object, even if copied. const Nalu& nalu(size_t i) const { return nalu_[i]; } + /// @return Transfer characteristics of the config. + uint8_t transfer_characteristics() const { return transfer_characteristics_; } + protected: DecoderConfigurationRecord(); @@ -61,6 +64,11 @@ class DecoderConfigurationRecord { nalu_length_size_ = nalu_length_size; } + /// Sets the transfer characteristics. + void set_transfer_characteristics(uint8_t transfer_characteristics) { + transfer_characteristics_ = transfer_characteristics; + } + private: // Performs the actual parsing of the data. virtual bool ParseInternal() = 0; @@ -69,7 +77,12 @@ class DecoderConfigurationRecord { // extracted Nalu can accessed. std::vector data_; std::vector nalu_; - uint8_t nalu_length_size_; + uint8_t nalu_length_size_ = 0; + + // Indicates the opto-electronic transfer characteristics of the source + // picture, which can be used to determine whether the video is HDR or SDR. + // The parameter is extracted from SPS. + uint8_t transfer_characteristics_ = 0; DISALLOW_COPY_AND_ASSIGN(DecoderConfigurationRecord); }; diff --git a/packager/media/codecs/h264_parser.cc b/packager/media/codecs/h264_parser.cc index aa45d039ed..05ac28324f 100644 --- a/packager/media/codecs/h264_parser.cc +++ b/packager/media/codecs/h264_parser.cc @@ -527,8 +527,11 @@ H264Parser::Result H264Parser::ParseVUIParameters(H26xBitReader* br, READ_BITS_OR_RETURN(3, &data); // video_format READ_BOOL_OR_RETURN(&data); // video_full_range_flag READ_BOOL_OR_RETURN(&data); // colour_description_present_flag - if (data) - READ_BITS_OR_RETURN(24, &data); // color description syntax elements + if (data) { + READ_BITS_OR_RETURN(8, &data); // colour primaries + READ_BITS_OR_RETURN(8, &sps->transfer_characteristics); + READ_BITS_OR_RETURN(8, &data); // matrix coeffs + } } READ_BOOL_OR_RETURN(&data); // chroma_loc_info_present_flag diff --git a/packager/media/codecs/h264_parser.h b/packager/media/codecs/h264_parser.h index f1b470c66d..52118b7c66 100644 --- a/packager/media/codecs/h264_parser.h +++ b/packager/media/codecs/h264_parser.h @@ -82,6 +82,8 @@ struct H264Sps { bool vui_parameters_present_flag; int sar_width; // Set to 0 when not specified. int sar_height; // Set to 0 when not specified. + int transfer_characteristics; + bool bitstream_restriction_flag; int max_num_reorder_frames; int max_dec_frame_buffering; diff --git a/packager/media/codecs/h264_parser_unittest.cc b/packager/media/codecs/h264_parser_unittest.cc index 5211109c8b..625703572a 100644 --- a/packager/media/codecs/h264_parser_unittest.cc +++ b/packager/media/codecs/h264_parser_unittest.cc @@ -187,6 +187,47 @@ TEST(H264ParserTest, PredWeightTable) { EXPECT_EQ(0, pred_weight_table.chroma_offset[3][1]); } +TEST(H264ParserTest, ParseSps) { + const uint8_t kSps[] = {0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4, + 0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91, + 0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA, + 0x60, 0x0F, 0x16, 0x2D, 0x96}; + + H264Parser parser; + int sps_id = 0; + Nalu nalu; + ASSERT_TRUE(nalu.Initialize(Nalu::kH264, kSps, arraysize(kSps))); + ASSERT_EQ(H264Parser::kOk, parser.ParseSps(nalu, &sps_id)); + + const H264Sps* sps = parser.GetSps(sps_id); + ASSERT_TRUE(sps); + + EXPECT_EQ(100, sps->profile_idc); + EXPECT_EQ(30, sps->level_idc); + EXPECT_EQ(0, sps->transfer_characteristics); +} + +TEST(H264ParserTest, ParseSpsWithTransferCharacteristics) { + const uint8_t kSps[] = { + 0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4, 0x2F, 0xF9, 0x7F, 0xF0, + 0x00, 0x80, 0x00, 0x96, 0xA1, 0x22, 0x01, 0x28, 0x00, 0x00, 0x03, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x01, 0x80, 0x78, 0xB1, 0x6C, 0xB0, + }; + + H264Parser parser; + int sps_id = 0; + Nalu nalu; + ASSERT_TRUE(nalu.Initialize(Nalu::kH264, kSps, arraysize(kSps))); + ASSERT_EQ(H264Parser::kOk, parser.ParseSps(nalu, &sps_id)); + + const H264Sps* sps = parser.GetSps(sps_id); + ASSERT_TRUE(sps); + + EXPECT_EQ(100, sps->profile_idc); + EXPECT_EQ(30, sps->level_idc); + EXPECT_EQ(16, sps->transfer_characteristics); +} + TEST(H264ParserTest, ExtractResolutionFromSpsData) { const uint8_t kSps[] = {0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4, 0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91, diff --git a/packager/media/codecs/h265_parser.cc b/packager/media/codecs/h265_parser.cc index b1b68663dd..d238572dab 100644 --- a/packager/media/codecs/h265_parser.cc +++ b/packager/media/codecs/h265_parser.cc @@ -661,8 +661,9 @@ H265Parser::Result H265Parser::ParseVuiParameters(int max_num_sub_layers_minus1, bool colour_description_present_flag; TRUE_OR_RETURN(br->ReadBool(&colour_description_present_flag)); if (colour_description_present_flag) { - // colour_primaries, transfer_characteristics, matrix_coeffs - TRUE_OR_RETURN(br->SkipBits(8 + 8 + 8)); + TRUE_OR_RETURN(br->SkipBits(8)); // colour_primaries + TRUE_OR_RETURN(br->ReadBits(8, &vui->transfer_characteristics)); + TRUE_OR_RETURN(br->SkipBits(8)); // matrix_coeffs } } diff --git a/packager/media/codecs/h265_parser.h b/packager/media/codecs/h265_parser.h index 6eae120d1a..779fc80a28 100644 --- a/packager/media/codecs/h265_parser.h +++ b/packager/media/codecs/h265_parser.h @@ -50,6 +50,7 @@ struct H265VuiParameters { int aspect_ratio_idc = 0; int sar_width = 0; int sar_height = 0; + int transfer_characteristics = 0; bool bitstream_restriction_flag = false; int min_spatial_segmentation_idc = 0; diff --git a/packager/media/codecs/h265_parser_unittest.cc b/packager/media/codecs/h265_parser_unittest.cc index 063f2f5a8b..771f0427b0 100644 --- a/packager/media/codecs/h265_parser_unittest.cc +++ b/packager/media/codecs/h265_parser_unittest.cc @@ -15,11 +15,17 @@ namespace H265 { namespace { -// Data taken from bear-640x360-hevc.mp4 +// Data taken from bear-640x360-hevc.mp4 and bear-640x360-hevc-hdr10.mp4. const uint8_t kSpsData[] = { 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xa0, 0x05, 0x02, 0x01, 0x69, 0x65, 0x95, 0xe4, 0x93, 0x2b, 0xc0, 0x40, 0x40, 0x00, 0x00, 0xfa, 0x40, 0x00, 0x1d, 0x4c, 0x02}; +const uint8_t kSpsDataWithTransferCharacteristics[] = { + 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xA0, 0x03, 0xC0, 0x80, 0x10, 0xE4, + 0xD9, 0x65, 0x66, 0x92, 0x4C, 0xAF, 0x01, 0x6A, 0x12, 0x20, 0x13, 0x6C, + 0x20, 0x00, 0x00, 0x7D, 0x20, 0x00, 0x0B, 0xB8, 0x0C, 0x25, 0x9A, 0x4B, + 0xC0, 0x01, 0xE8, 0x48, 0x00, 0x3D, 0x09, 0x10}; const uint8_t kPpsData[] = {0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89}; const uint8_t kSliceData[] = { // Incomplete segment data. @@ -105,6 +111,32 @@ TEST(H265ParserTest, ParseSps) { EXPECT_EQ(4, sps->log2_max_pic_order_cnt_lsb_minus4); EXPECT_EQ(3, sps->log2_diff_max_min_luma_transform_block_size); EXPECT_EQ(0, sps->max_transform_hierarchy_depth_intra); + EXPECT_EQ(0, sps->vui_parameters.transfer_characteristics); +} + +TEST(H265ParserTest, ParseSpsWithTransferCharacteristics) { + Nalu nalu; + ASSERT_TRUE(nalu.Initialize(Nalu::kH265, kSpsDataWithTransferCharacteristics, + arraysize(kSpsDataWithTransferCharacteristics))); + ASSERT_EQ(Nalu::H265_SPS, nalu.type()); + + int id = 12; + H265Parser parser; + ASSERT_EQ(H265Parser::kOk, parser.ParseSps(nalu, &id)); + ASSERT_EQ(0, id); + + const H265Sps* sps = parser.GetSps(id); + ASSERT_TRUE(sps); + + EXPECT_EQ(0, sps->video_parameter_set_id); + EXPECT_EQ(0, sps->max_sub_layers_minus1); + EXPECT_EQ(0, sps->seq_parameter_set_id); + EXPECT_EQ(1, sps->chroma_format_idc); + EXPECT_EQ(1080, sps->pic_height_in_luma_samples); + EXPECT_EQ(4, sps->log2_max_pic_order_cnt_lsb_minus4); + EXPECT_EQ(3, sps->log2_diff_max_min_luma_transform_block_size); + EXPECT_EQ(0, sps->max_transform_hierarchy_depth_intra); + EXPECT_EQ(16, sps->vui_parameters.transfer_characteristics); } TEST(H265ParserTest, ParsePps) { diff --git a/packager/media/codecs/hevc_decoder_configuration_record.cc b/packager/media/codecs/hevc_decoder_configuration_record.cc index c6037a4f43..15f2b8bcfd 100644 --- a/packager/media/codecs/hevc_decoder_configuration_record.cc +++ b/packager/media/codecs/hevc_decoder_configuration_record.cc @@ -10,6 +10,7 @@ #include "packager/base/strings/string_util.h" #include "packager/media/base/buffer_reader.h" #include "packager/media/base/rcheck.h" +#include "packager/media/codecs/h265_parser.h" namespace shaka { namespace media { @@ -61,15 +62,9 @@ std::string ReverseBitsAndHexEncode(uint32_t x) { } // namespace -HEVCDecoderConfigurationRecord::HEVCDecoderConfigurationRecord() - : version_(0), - general_profile_space_(0), - general_tier_flag_(false), - general_profile_idc_(0), - general_profile_compatibility_flags_(0), - general_level_idc_(0) {} +HEVCDecoderConfigurationRecord::HEVCDecoderConfigurationRecord() = default; -HEVCDecoderConfigurationRecord::~HEVCDecoderConfigurationRecord() {} +HEVCDecoderConfigurationRecord::~HEVCDecoderConfigurationRecord() = default; bool HEVCDecoderConfigurationRecord::ParseInternal() { BufferReader reader(data(), data_size()); @@ -113,6 +108,14 @@ bool HEVCDecoderConfigurationRecord::ParseInternal() { RCHECK(nalu.Initialize(Nalu::kH265, data() + nalu_offset, nalu_length)); RCHECK(nalu.type() == nal_unit_type); AddNalu(nalu); + + if (nalu.type() == Nalu::H265_SPS) { + H265Parser parser; + int sps_id = 0; + RCHECK(parser.ParseSps(nalu, &sps_id) == H265Parser::kOk); + set_transfer_characteristics( + parser.GetSps(sps_id)->vui_parameters.transfer_characteristics); + } } } diff --git a/packager/media/codecs/hevc_decoder_configuration_record.h b/packager/media/codecs/hevc_decoder_configuration_record.h index 188f1342d0..da349f9c1c 100644 --- a/packager/media/codecs/hevc_decoder_configuration_record.h +++ b/packager/media/codecs/hevc_decoder_configuration_record.h @@ -31,13 +31,13 @@ class HEVCDecoderConfigurationRecord : public DecoderConfigurationRecord { private: bool ParseInternal() override; - uint8_t version_; - uint8_t general_profile_space_; - bool general_tier_flag_; - uint8_t general_profile_idc_; - uint32_t general_profile_compatibility_flags_; + uint8_t version_ = 0; + uint8_t general_profile_space_ = 0; + bool general_tier_flag_ = false; + uint8_t general_profile_idc_ = 0; + uint32_t general_profile_compatibility_flags_ = 0; std::vector general_constraint_indicator_flags_; - uint8_t general_level_idc_; + uint8_t general_level_idc_ = 0; DISALLOW_COPY_AND_ASSIGN(HEVCDecoderConfigurationRecord); }; diff --git a/packager/media/codecs/hevc_decoder_configuration_record_unittest.cc b/packager/media/codecs/hevc_decoder_configuration_record_unittest.cc index fededbca84..fc6a84920e 100644 --- a/packager/media/codecs/hevc_decoder_configuration_record_unittest.cc +++ b/packager/media/codecs/hevc_decoder_configuration_record_unittest.cc @@ -12,6 +12,7 @@ namespace shaka { namespace media { TEST(HEVCDecoderConfigurationRecordTest, Success) { + // clang-format off const uint8_t kHevcDecoderConfigurationData[] = { 0x01, // Version 0x02, // profile_indication @@ -33,11 +34,61 @@ TEST(HEVCDecoderConfigurationRecordTest, Success) { // array 2 0x21, // nal type 0x00, 0x01, // num nalus - // Nalu 1 - 0x00, 0x0f, // nal unit length - 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, - 0x00, 0x00, 0x03, 0x00, 0x00, + // nalu 1 + 0x00, 0x24, // nal unit length + 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3f, 0xa0, 0x05, 0x02, 0x01, 0x69, 0x65, 0x95, + 0xe4, 0x93, 0x2b, 0xc0, 0x40, 0x40, 0x00, 0x00, 0xfa, 0x40, 0x00, + 0x1d, 0x4c, 0x02, }; + // clang-format on + HEVCDecoderConfigurationRecord hevc_config; + ASSERT_TRUE(hevc_config.Parse(kHevcDecoderConfigurationData, + arraysize(kHevcDecoderConfigurationData))); + EXPECT_EQ(4u, hevc_config.nalu_length_size()); + 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()); + EXPECT_EQ(0x40, hevc_config.nalu(0).data()[0]); +} + +TEST(HEVCDecoderConfigurationRecordTest, SuccessWithTransferCharacteristics) { + // clang-format off + const uint8_t kHevcDecoderConfigurationData[] = { + 0x01, // Version + 0x02, // profile_indication + 0x20, 0x00, 0x00, 0x00, // general_profile_compatibility_flags + // general_constraint_indicator_flags + 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, // general_level_idc + 0xF0, 0x00, 0xFC, 0xFD, 0xFA, 0xFA, 0x00, 0x00, + 0x0F, // length_size_minus_one + 0x03, // num_of_arrays + // array 1 + 0xA0, // nal type + 0x00, 0x01, // num nalus + 0x00, 0x18, // nal unit length + 0x40, 0x01, 0x0C, 0x01, 0xFF, 0xFF, 0x02, 0x20, 0x00, 0x00, 0x03, + 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0x95, + 0x98, 0x09, + // array 2 + 0xA1, // nal type + 0x00, 0x01, // num nalus + 0x00, 0x38, // nal unit length + 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xA0, 0x03, 0xC0, 0x80, + 0x10, 0xE4, 0xD9, 0x65, 0x66, 0x92, 0x4C, 0xAF, 0x01, 0x6A, 0x12, + 0x20, 0x13, 0x6C, 0x20, 0x00, 0x00, 0x7D, 0x20, 0x00, 0x0B, 0xB8, + 0x0C, 0x25, 0x9A, 0x4B, 0xC0, 0x01, 0xE8, 0x48, 0x00, 0x3D, 0x09, + 0x10, + // array 3 + 0xA2, // nal type + 0x00, 0x01, // num nalus + 0x00, 0x07, // nal unit length + 0x44, 0x01, 0xC1, 0x72, 0xA6, 0x46, 0x24, + }; + // clang-format on HEVCDecoderConfigurationRecord hevc_config; ASSERT_TRUE(hevc_config.Parse(kHevcDecoderConfigurationData, @@ -48,9 +99,11 @@ TEST(HEVCDecoderConfigurationRecordTest, Success) { 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(3u, hevc_config.nalu_count()); EXPECT_EQ(0x16u, hevc_config.nalu(0).payload_size()); EXPECT_EQ(0x40, hevc_config.nalu(0).data()[0]); + + EXPECT_EQ(16, hevc_config.transfer_characteristics()); } TEST(HEVCDecoderConfigurationRecordTest, FailOnInsufficientData) { diff --git a/packager/media/crypto/subsample_generator_unittest.cc b/packager/media/crypto/subsample_generator_unittest.cc index 5f89ffb98c..ea94edd92c 100644 --- a/packager/media/crypto/subsample_generator_unittest.cc +++ b/packager/media/crypto/subsample_generator_unittest.cc @@ -63,6 +63,7 @@ VideoStreamInfo GetVideoStreamInfo(Codec codec) { const uint16_t kHeight = 20u; const uint32_t kPixelWidth = 2u; const uint32_t kPixelHeight = 3u; + const uint8_t kTransferCharacteristics = 0; const int16_t kTrickPlayFactor = 0; const uint8_t kNaluLengthSize = 1u; @@ -81,11 +82,11 @@ VideoStreamInfo GetVideoStreamInfo(Codec codec) { // We do not care about the codec configs for other codecs in this file. break; } - return VideoStreamInfo(kTrackId, kTimeScale, kDuration, codec, - H26xStreamFormat::kUnSpecified, kCodecString, - codec_config, codec_config_size, kWidth, kHeight, - kPixelWidth, kPixelHeight, kTrickPlayFactor, - kNaluLengthSize, kLanguage, !kEncrypted); + return VideoStreamInfo( + kTrackId, kTimeScale, kDuration, codec, H26xStreamFormat::kUnSpecified, + kCodecString, codec_config, codec_config_size, kWidth, kHeight, + kPixelWidth, kPixelHeight, kTransferCharacteristics, kTrickPlayFactor, + kNaluLengthSize, kLanguage, !kEncrypted); } AudioStreamInfo GetAudioStreamInfo(Codec codec) { diff --git a/packager/media/event/media_event.gyp b/packager/media/event/media_event.gyp index 13086b0cc5..5206c82ab4 100644 --- a/packager/media/event/media_event.gyp +++ b/packager/media/event/media_event.gyp @@ -54,6 +54,7 @@ 'type': '<(gtest_target_type)', 'sources': [ 'hls_notify_muxer_listener_unittest.cc', + 'muxer_listener_internal_unittest.cc', 'mpd_notify_muxer_listener_unittest.cc', 'muxer_listener_test_helper.cc', 'muxer_listener_test_helper.h', diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index 54928b306c..8681c07dc9 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -87,6 +87,10 @@ void AddVideoInfo(const VideoStreamInfo* video_stream_info, if (video_stream_info->playback_rate() > 0) { video_info->set_playback_rate(video_stream_info->playback_rate()); } + if (video_stream_info->transfer_characteristics() > 0) { + video_info->set_transfer_characteristics( + video_stream_info->transfer_characteristics()); + } } void AddAudioInfo(const AudioStreamInfo* audio_stream_info, diff --git a/packager/media/event/muxer_listener_internal_unittest.cc b/packager/media/event/muxer_listener_internal_unittest.cc new file mode 100644 index 0000000000..143734805f --- /dev/null +++ b/packager/media/event/muxer_listener_internal_unittest.cc @@ -0,0 +1,77 @@ +// Copyright 2019 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/event/muxer_listener_internal.h" + +#include + +#include "packager/media/event/muxer_listener_test_helper.h" +#include "packager/mpd/base/media_info.pb.h" + +namespace shaka { +namespace media { +namespace internal { +namespace { +const uint32_t kReferenceTimeScale = 1000; +} // namespace + +class MuxerListenerInternalTest : public ::testing::Test {}; + +class MuxerListenerInternalVideoStreamTest : public MuxerListenerInternalTest { + protected: + std::shared_ptr video_stream_info_ = + CreateVideoStreamInfo(GetDefaultVideoStreamInfoParams()); +}; + +TEST_F(MuxerListenerInternalVideoStreamTest, Basic) { + MediaInfo media_info; + ASSERT_TRUE(GenerateMediaInfo(MuxerOptions(), *video_stream_info_, + kReferenceTimeScale, + MuxerListener::kContainerMp4, &media_info)); + ASSERT_TRUE(media_info.has_video_info()); + const MediaInfo_VideoInfo& video_info = media_info.video_info(); + EXPECT_EQ("avc1.010101", video_info.codec()); + EXPECT_EQ(720u, video_info.width()); + EXPECT_EQ(480u, video_info.height()); + EXPECT_EQ(10u, video_info.time_scale()); + EXPECT_EQ(1u, video_info.pixel_width()); + EXPECT_EQ(1u, video_info.pixel_height()); + EXPECT_EQ(0u, video_info.playback_rate()); + EXPECT_EQ(0u, video_info.transfer_characteristics()); +} + +TEST_F(MuxerListenerInternalVideoStreamTest, PixelWidthHeight) { + MediaInfo media_info; + video_stream_info_->set_pixel_width(100); + video_stream_info_->set_pixel_height(200); + ASSERT_TRUE(GenerateMediaInfo(MuxerOptions(), *video_stream_info_, + kReferenceTimeScale, + MuxerListener::kContainerMp4, &media_info)); + EXPECT_EQ(100u, media_info.video_info().pixel_width()); + EXPECT_EQ(200u, media_info.video_info().pixel_height()); +} + +TEST_F(MuxerListenerInternalVideoStreamTest, PlaybackRate) { + MediaInfo media_info; + video_stream_info_->set_playback_rate(5); + ASSERT_TRUE(GenerateMediaInfo(MuxerOptions(), *video_stream_info_, + kReferenceTimeScale, + MuxerListener::kContainerMp4, &media_info)); + EXPECT_EQ(5u, media_info.video_info().playback_rate()); +} + +TEST_F(MuxerListenerInternalVideoStreamTest, TransferCharacteristics) { + MediaInfo media_info; + video_stream_info_->set_transfer_characteristics(18); + ASSERT_TRUE(GenerateMediaInfo(MuxerOptions(), *video_stream_info_, + kReferenceTimeScale, + MuxerListener::kContainerMp4, &media_info)); + EXPECT_EQ(18u, media_info.video_info().transfer_characteristics()); +} + +} // namespace internal +} // namespace media +} // namespace shaka diff --git a/packager/media/event/muxer_listener_test_helper.cc b/packager/media/event/muxer_listener_test_helper.cc index 2c2703fbd5..3eed87415e 100644 --- a/packager/media/event/muxer_listener_test_helper.cc +++ b/packager/media/event/muxer_listener_test_helper.cc @@ -15,13 +15,14 @@ namespace media { VideoStreamInfoParameters::VideoStreamInfoParameters() {} VideoStreamInfoParameters::~VideoStreamInfoParameters() {} -std::shared_ptr CreateVideoStreamInfo( +std::shared_ptr CreateVideoStreamInfo( const VideoStreamInfoParameters& param) { return std::make_shared( param.track_id, param.time_scale, param.duration, param.codec, H26xStreamFormat::kUnSpecified, param.codec_string, param.codec_config.data(), param.codec_config.size(), param.width, param.height, param.pixel_width, param.pixel_height, + 0, // transfer_characteristics 0, // trick_play_factor param.nalu_length_size, param.language, param.is_encrypted); } diff --git a/packager/media/event/muxer_listener_test_helper.h b/packager/media/event/muxer_listener_test_helper.h index d5678bf52b..ca3a15d872 100644 --- a/packager/media/event/muxer_listener_test_helper.h +++ b/packager/media/event/muxer_listener_test_helper.h @@ -80,7 +80,7 @@ struct OnMediaEndParameters { }; // Creates StreamInfo instance from VideoStreamInfoParameters. -std::shared_ptr CreateVideoStreamInfo( +std::shared_ptr CreateVideoStreamInfo( const VideoStreamInfoParameters& param); // Returns the "default" VideoStreamInfoParameters for testing. diff --git a/packager/media/formats/mp2t/es_parser_h264.cc b/packager/media/formats/mp2t/es_parser_h264.cc index 8a609a7cdd..2bd3d26c11 100644 --- a/packager/media/formats/mp2t/es_parser_h264.cc +++ b/packager/media/formats/mp2t/es_parser_h264.cc @@ -159,8 +159,8 @@ bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) { codec_fourcc, decoder_config_record[1], decoder_config_record[2], decoder_config_record[3]), decoder_config_record.data(), decoder_config_record.size(), coded_width, - coded_height, pixel_width, pixel_height, 0, nalu_length_size, - std::string(), false); + coded_height, pixel_width, pixel_height, sps->transfer_characteristics, 0, + nalu_length_size, std::string(), false); DVLOG(1) << "Profile IDC: " << sps->profile_idc; DVLOG(1) << "Level IDC: " << sps->level_idc; DVLOG(1) << "log2_max_frame_num_minus4: " << sps->log2_max_frame_num_minus4; diff --git a/packager/media/formats/mp2t/es_parser_h265.cc b/packager/media/formats/mp2t/es_parser_h265.cc index 6da887302d..4983392f22 100644 --- a/packager/media/formats/mp2t/es_parser_h265.cc +++ b/packager/media/formats/mp2t/es_parser_h265.cc @@ -39,7 +39,7 @@ EsParserH265::~EsParserH265() {} void EsParserH265::Reset() { DVLOG(1) << "EsParserH265::Reset"; h265_parser_.reset(new H265Parser()); - last_video_decoder_config_ = std::shared_ptr(); + last_video_decoder_config_ = std::shared_ptr(); decoder_config_check_pending_ = false; EsParserH26x::Reset(); } @@ -162,7 +162,8 @@ bool EsParserH265::UpdateVideoDecoderConfig(int pps_id) { 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); + pixel_height, sps->vui_parameters.transfer_characteristics, 0, + nalu_length_size, std::string(), false); // Video config notification. new_stream_info_cb_.Run(last_video_decoder_config_); diff --git a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc index d0f3bf30dc..05656729ed 100644 --- a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc +++ b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc @@ -83,6 +83,7 @@ const uint32_t kWidth = 1280; const uint32_t kHeight = 720; const uint32_t kPixelWidth = 1; const uint32_t kPixelHeight = 1; +const uint8_t kTransferCharacteristics = 0; const uint16_t kTrickPlayFactor = 1; const uint8_t kNaluLengthSize = 1; const bool kIsEncrypted = false; @@ -123,7 +124,8 @@ std::shared_ptr CreateVideoStreamInfo(Codec codec) { kTrackId, kTimeScale, kDuration, codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kVideoExtraData, arraysize(kVideoExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + kIsEncrypted)); return stream_info; } @@ -355,7 +357,8 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) { kTrackId, kTestTimescale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kVideoExtraData, arraysize(kVideoExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTransferCharacteristics, kTrickPlayFactor, 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 46a990fc17..aa0fd31a9f 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -44,6 +44,7 @@ const uint32_t kWidth = 1280; const uint32_t kHeight = 720; const uint32_t kPixelWidth = 1; const uint32_t kPixelHeight = 1; +const uint8_t kTransferCharacteristics = 0; const uint16_t kTrickPlayFactor = 1; const uint8_t kNaluLengthSize = 1; const bool kIsEncrypted = false; @@ -110,7 +111,8 @@ TEST_F(TsSegmenterTest, Initialize) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -129,7 +131,8 @@ TEST_F(TsSegmenterTest, AddSample) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -180,7 +183,8 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { kTrackId, kInputTimescale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; @@ -274,7 +278,8 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -301,7 +306,8 @@ TEST_F(TsSegmenterTest, FinalizeSegment) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -328,7 +334,8 @@ TEST_F(TsSegmenterTest, EncryptedSample) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 77371f8c68..4480f4d112 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -555,6 +555,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } std::string codec_string; uint8_t nalu_length_size = 0; + uint8_t transfer_characteristics = 0; const FourCC actual_format = entry.GetActualFormat(); const Codec video_codec = FourCCToCodec(actual_format); @@ -577,6 +578,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } codec_string = avc_config.GetCodecString(actual_format); nalu_length_size = avc_config.nalu_length_size(); + transfer_characteristics = avc_config.transfer_characteristics(); // Use configurations from |avc_config| if it is valid. if (avc_config.coded_width() != 0) { @@ -624,6 +626,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } codec_string = hevc_config.GetCodecString(actual_format); nalu_length_size = hevc_config.nalu_length_size(); + transfer_characteristics = hevc_config.transfer_characteristics(); if (!entry.extra_codec_configs.empty()) { if (!UpdateCodecStringForDolbyVision( @@ -672,6 +675,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { GetH26xStreamFormat(actual_format), codec_string, codec_configuration_data.data(), codec_configuration_data.size(), coded_width, coded_height, pixel_width, pixel_height, + transfer_characteristics, 0, // trick_play_factor nalu_length_size, track->media.header.language.code, is_encrypted)); video_stream_info->set_extra_config(entry.ExtraCodecConfigsAsVector()); diff --git a/packager/media/formats/webm/segmenter_test_base.cc b/packager/media/formats/webm/segmenter_test_base.cc index 643b0b0fc0..c8a589630a 100644 --- a/packager/media/formats/webm/segmenter_test_base.cc +++ b/packager/media/formats/webm/segmenter_test_base.cc @@ -30,6 +30,7 @@ const uint16_t kWidth = 100; const uint16_t kHeight = 100; const uint16_t kPixelWidth = 100; const uint16_t kPixelHeight = 100; +const uint8_t kTransferCharacteristics = 0; const int16_t kTrickPlayFactor = 1; const uint8_t kNaluLengthSize = 0; @@ -85,8 +86,8 @@ VideoStreamInfo* SegmentTestBase::CreateVideoStreamInfo( return new VideoStreamInfo( kTrackId, time_scale, kDurationInSeconds * time_scale, kCodec, H26xStreamFormat::kUnSpecified, kCodecString, NULL, 0, kWidth, kHeight, - kPixelWidth, kPixelHeight, kTrickPlayFactor, kNaluLengthSize, kLanguage, - false); + kPixelWidth, kPixelHeight, kTransferCharacteristics, kTrickPlayFactor, + 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 7b6713b5ea..76d3dc94e2 100644 --- a/packager/media/formats/webm/webm_cluster_parser_unittest.cc +++ b/packager/media/formats/webm/webm_cluster_parser_unittest.cc @@ -91,6 +91,7 @@ const uint16_t kWidth = 320u; const uint16_t kHeight = 180u; const uint32_t kPixelWidth = 1u; const uint32_t kPixelHeight = 1u; +const uint8_t kTransferCharacteristics = 0; const int16_t kTrickPlayFactor = 0u; const uint8_t kNaluLengthSize = 0u; @@ -350,6 +351,7 @@ class WebMClusterParserTest : public testing::Test { kHeight, kPixelWidth, kPixelHeight, + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, diff --git a/packager/media/formats/webm/webm_video_client.cc b/packager/media/formats/webm/webm_video_client.cc index 0298a3e486..4177477def 100644 --- a/packager/media/formats/webm/webm_video_client.cc +++ b/packager/media/formats/webm/webm_video_client.cc @@ -124,7 +124,7 @@ std::shared_ptr WebMVideoClient::GetVideoStreamInfo( track_num, kWebMTimeScale, 0, video_codec, H26xStreamFormat::kUnSpecified, codec_string, codec_private.data(), codec_private.size(), width_after_crop, height_after_crop, pixel_width, pixel_height, 0, 0, - std::string(), is_encrypted); + 0 /* transfer_characteristics */, std::string(), is_encrypted); } VPCodecConfigurationRecord WebMVideoClient::GetVpCodecConfig( diff --git a/packager/media/formats/wvm/wvm_media_parser.cc b/packager/media/formats/wvm/wvm_media_parser.cc index 1d7139cd05..85d353a920 100644 --- a/packager/media/formats/wvm/wvm_media_parser.cc +++ b/packager/media/formats/wvm/wvm_media_parser.cc @@ -744,9 +744,9 @@ bool WvmMediaParser::ParseIndexEntry() { 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_factor, - nalu_length_size, std::string(), - decryption_key_source_ ? false : true)); + video_height, pixel_width, pixel_height, + 0 /* transfer_characteristics */, trick_play_factor, nalu_length_size, + std::string(), decryption_key_source_ ? false : true)); program_demux_stream_map_[base::UintToString(index_program_id_) + ":" + base::UintToString( video_pes_stream_id diff --git a/packager/media/test/data/README b/packager/media/test/data/README index d4bce77108..b348011794 100644 --- a/packager/media/test/data/README +++ b/packager/media/test/data/README @@ -35,6 +35,11 @@ bear-1280x720.mp4 - AVC + AAC encode, mulitplexed into an ISOBMFF container. bear-640x360.mp4 - Same as above, but in a different resolution. bear-640x360-ec3.mp4 - Same content, but audio encoded with E-AC3. bear-640x360-hevc.mp4 - Same content, but video encoded with HEVC. +bear-640x360-hevc-hdr10.mp4 - Same content, but video encoded with HEVC with HDR10 using the below command: + ffmpeg -i bear-640x360-hevc.mp4 -c:a copy -c:v libx265 -tag:v hvc1 -crf 22 \ + -pix_fmt yuv420p10le \ + -x265-params "colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc" \ + bear-640x360-hevc-hdr10.mp4 bear-320x180.mp4 - Same as above, but in a different resolution. bear-640x360-trailing-moov.mp4 - Same content, but with moov box in the end. bear-640x360-trailing-moov-additional-mdat.mp4 - Same content, but with moov box in the end and an additional unused mdat, which should be ignored. diff --git a/packager/media/test/data/bear-640x360-hevc-hdr10.mp4 b/packager/media/test/data/bear-640x360-hevc-hdr10.mp4 new file mode 100644 index 0000000000..ac3a796089 Binary files /dev/null and b/packager/media/test/data/bear-640x360-hevc-hdr10.mp4 differ diff --git a/packager/mpd/base/media_info.proto b/packager/mpd/base/media_info.proto index b503606906..de1e208ec8 100644 --- a/packager/mpd/base/media_info.proto +++ b/packager/mpd/base/media_info.proto @@ -45,6 +45,10 @@ message MediaInfo { // playback_rate: the playout capability (e.g., 4x, 8x, 16x fast foward) of // the trick play stream. optional uint32 playback_rate = 9; + + // Transfer characteristics. Useful to determine the VIDEO-RANGE for HLS, + // i.e. whether it is SDR or HDR. + optional uint32 transfer_characteristics = 10; } message AudioInfo {