From 404660cbdb989951104cf5c7f5f2bef3fb7b63f6 Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Fri, 1 Apr 2016 10:00:49 -0700 Subject: [PATCH] Add H.265 byte to unit stream converter. Also renames NaluReader::NaluType to CodecType. Issue #46 Change-Id: I37d21fcb1109659f14baf43308a94c5e2d9ac2d1 --- packager/media/filters/filters.gyp | 5 + .../h264_byte_to_unit_stream_converter.cc | 97 +++++--------- .../h264_byte_to_unit_stream_converter.h | 39 ++---- ..._byte_to_unit_stream_converter_unittest.cc | 4 +- .../h265_byte_to_unit_stream_converter.cc | 120 ++++++++++++++++++ .../h265_byte_to_unit_stream_converter.h | 46 +++++++ ..._byte_to_unit_stream_converter_unittest.cc | 75 +++++++++++ .../h26x_byte_to_unit_stream_converter.cc | 62 +++++++++ .../h26x_byte_to_unit_stream_converter.h | 64 ++++++++++ .../nal_unit_to_byte_stream_converter.cc | 2 +- packager/media/filters/nalu_reader.cc | 2 +- packager/media/filters/nalu_reader.h | 6 +- packager/media/formats/mp2t/es_parser_h264.cc | 2 +- .../formats/mp4/encrypting_fragmenter.cc | 2 +- .../media/formats/wvm/wvm_media_parser.cc | 2 +- packager/media/test/data/README | 3 + .../test/data/hevc-byte-stream-frame.h265 | Bin 0 -> 11403 bytes .../test/data/hevc-unit-stream-frame.h265 | Bin 0 -> 11314 bytes 18 files changed, 427 insertions(+), 104 deletions(-) create mode 100644 packager/media/filters/h265_byte_to_unit_stream_converter.cc create mode 100644 packager/media/filters/h265_byte_to_unit_stream_converter.h create mode 100644 packager/media/filters/h265_byte_to_unit_stream_converter_unittest.cc create mode 100644 packager/media/filters/h26x_byte_to_unit_stream_converter.cc create mode 100644 packager/media/filters/h26x_byte_to_unit_stream_converter.h create mode 100644 packager/media/test/data/hevc-byte-stream-frame.h265 create mode 100644 packager/media/test/data/hevc-unit-stream-frame.h265 diff --git a/packager/media/filters/filters.gyp b/packager/media/filters/filters.gyp index 8410654f7e..faf9acbede 100644 --- a/packager/media/filters/filters.gyp +++ b/packager/media/filters/filters.gyp @@ -23,10 +23,14 @@ 'h264_byte_to_unit_stream_converter.h', 'h264_parser.cc', 'h264_parser.h', + 'h265_byte_to_unit_stream_converter.cc', + 'h265_byte_to_unit_stream_converter.h', 'h265_parser.cc', 'h265_parser.h', 'h26x_bit_reader.cc', 'h26x_bit_reader.h', + 'h26x_byte_to_unit_stream_converter.cc', + 'h26x_byte_to_unit_stream_converter.h', 'hevc_decoder_configuration.cc', 'hevc_decoder_configuration.h', 'nal_unit_to_byte_stream_converter.cc', @@ -53,6 +57,7 @@ 'ec3_audio_util_unittest.cc', 'h264_byte_to_unit_stream_converter_unittest.cc', 'h264_parser_unittest.cc', + 'h265_byte_to_unit_stream_converter_unittest.cc', 'h265_parser_unittest.cc', 'h26x_bit_reader_unittest.cc', 'hevc_decoder_configuration_unittest.cc', diff --git a/packager/media/filters/h264_byte_to_unit_stream_converter.cc b/packager/media/filters/h264_byte_to_unit_stream_converter.cc index 7be8bb1688..3fd734e45c 100644 --- a/packager/media/filters/h264_byte_to_unit_stream_converter.cc +++ b/packager/media/filters/h264_byte_to_unit_stream_converter.cc @@ -15,74 +15,12 @@ namespace edash_packager { namespace media { -namespace { -// Additional space to reserve for output frame. This value ought to be enough -// to acommodate frames consisting of 100 NAL units with 3-byte start codes. -const size_t kStreamConversionOverhead = 100; -} - -H264ByteToUnitStreamConverter::H264ByteToUnitStreamConverter() {} - +H264ByteToUnitStreamConverter::H264ByteToUnitStreamConverter() + : H26xByteToUnitStreamConverter(NaluReader::kH264) {} H264ByteToUnitStreamConverter::~H264ByteToUnitStreamConverter() {} -bool H264ByteToUnitStreamConverter::ConvertByteStreamToNalUnitStream( - const uint8_t* input_frame, - size_t input_frame_size, - std::vector* output_frame) { - DCHECK(input_frame); - DCHECK(output_frame); - - BufferWriter output_buffer(input_frame_size + kStreamConversionOverhead); - - Nalu nalu; - NaluReader reader(NaluReader::kH264, kIsAnnexbByteStream, input_frame, - input_frame_size); - if (!reader.StartsWithStartCode()) { - LOG(ERROR) << "H.264 byte stream frame did not begin with start code."; - return false; - } - while (reader.Advance(&nalu) == NaluReader::kOk) { - ProcessNalu(nalu, &output_buffer); - } - - output_buffer.SwapBuffer(output_frame); - return true; -} - -void H264ByteToUnitStreamConverter::ProcessNalu(const Nalu& nalu, - BufferWriter* output_buffer) { - DCHECK(nalu.data()); - DCHECK(output_buffer); - - // Skip the start code, but keep the 1-byte NALU type. - const uint8_t* nalu_ptr = nalu.data(); - const uint64_t nalu_size = nalu.payload_size() + nalu.header_size(); - DCHECK_LE(nalu_size, std::numeric_limits::max()); - - switch (nalu.type()) { - case Nalu::H264_SPS: - // Grab SPS NALU. - last_sps_.assign(nalu_ptr, nalu_ptr + nalu_size); - return; - case Nalu::H264_PPS: - // Grab PPS NALU. - last_pps_.assign(nalu_ptr, nalu_ptr + nalu_size); - return; - case Nalu::H264_AUD: - // Ignore AUD NALU. - return; - default: - // Copy all other NALUs. - break; - } - - // Append 4-byte length and NAL unit data to the buffer. - output_buffer->AppendInt(static_cast(nalu_size)); - output_buffer->AppendArray(nalu_ptr, nalu_size); -} - -bool H264ByteToUnitStreamConverter::GetAVCDecoderConfigurationRecord( - std::vector* decoder_config) { +bool H264ByteToUnitStreamConverter::GetDecoderConfigurationRecord( + std::vector* decoder_config) const { DCHECK(decoder_config); if ((last_sps_.size() < 4) || last_pps_.empty()) { @@ -108,10 +46,35 @@ bool H264ByteToUnitStreamConverter::GetAVCDecoderConfigurationRecord( buffer.AppendInt(num_pps); buffer.AppendInt(static_cast(last_pps_.size())); buffer.AppendVector(last_pps_); - buffer.SwapBuffer(decoder_config); + buffer.SwapBuffer(decoder_config); return true; } +bool H264ByteToUnitStreamConverter::ProcessNalu(const Nalu& nalu) { + DCHECK(nalu.data()); + + // Skip the start code, but keep the 1-byte NALU type. + const uint8_t* nalu_ptr = nalu.data(); + const uint64_t nalu_size = nalu.payload_size() + nalu.header_size(); + + switch (nalu.type()) { + case Nalu::H264_SPS: + // Grab SPS NALU. + last_sps_.assign(nalu_ptr, nalu_ptr + nalu_size); + return true; + case Nalu::H264_PPS: + // Grab PPS NALU. + last_pps_.assign(nalu_ptr, nalu_ptr + nalu_size); + return true; + case Nalu::H264_AUD: + // Ignore AUD NALU. + return true; + default: + // Have the base class handle other NALU types. + return false; + } +} + } // namespace media } // namespace edash_packager diff --git a/packager/media/filters/h264_byte_to_unit_stream_converter.h b/packager/media/filters/h264_byte_to_unit_stream_converter.h index 1cd5816a79..88837ce662 100644 --- a/packager/media/filters/h264_byte_to_unit_stream_converter.h +++ b/packager/media/filters/h264_byte_to_unit_stream_converter.h @@ -12,46 +12,31 @@ #include +#include "packager/media/filters/h26x_byte_to_unit_stream_converter.h" + namespace edash_packager { namespace media { -class BufferWriter; -class Nalu; - /// Class which converts H.264 byte streams (as specified in ISO/IEC 14496-10 /// Annex B) into H.264 NAL unit streams (as specified in ISO/IEC 14496-15). -class H264ByteToUnitStreamConverter { +class H264ByteToUnitStreamConverter : public H26xByteToUnitStreamConverter { public: - static const size_t kUnitStreamNaluLengthSize = 4; - H264ByteToUnitStreamConverter(); - ~H264ByteToUnitStreamConverter(); + ~H264ByteToUnitStreamConverter() override; - /// Converts a whole AVC byte stream encoded video frame to NAL unit stream - /// format. - /// @param input_frame is a buffer containing a whole H.264 frame in byte - /// stream format. - /// @param input_frame_size is the size of the H.264 frame, in bytes. - /// @param output_frame is a pointer to a vector which will receive the - /// converted frame. - /// @return true if successful, false otherwise. - bool ConvertByteStreamToNalUnitStream(const uint8_t* input_frame, - size_t input_frame_size, - std::vector* output_frame); - - /// Synthesizes an AVCDecoderConfigurationRecord from the SPS and PPS NAL - /// units extracted from the AVC byte stream. - /// @param decoder_config is a pointer to a vector, which on successful - /// return will contain the computed AVCDecoderConfigurationRecord. - /// @return true if successful, or false otherwise. - bool GetAVCDecoderConfigurationRecord(std::vector* decoder_config); + /// @name H26xByteToUnitStreamConverter implementation override. + /// @{ + bool GetDecoderConfigurationRecord( + std::vector* decoder_config) const override; + /// @} private: - void ProcessNalu(const Nalu& nalu, - BufferWriter* output_buffer); + bool ProcessNalu(const Nalu& nalu) override; std::vector last_sps_; std::vector last_pps_; + + DISALLOW_COPY_AND_ASSIGN(H264ByteToUnitStreamConverter); }; } // namespace media diff --git a/packager/media/filters/h264_byte_to_unit_stream_converter_unittest.cc b/packager/media/filters/h264_byte_to_unit_stream_converter_unittest.cc index 2a20e70a57..d5606bb0a0 100644 --- a/packager/media/filters/h264_byte_to_unit_stream_converter_unittest.cc +++ b/packager/media/filters/h264_byte_to_unit_stream_converter_unittest.cc @@ -40,7 +40,7 @@ TEST(H264ByteToUnitStreamConverter, ConversionSuccess) { ASSERT_TRUE(base::HexStringToBytes(kExpectedConfigRecord, &expected_decoder_config)); std::vector decoder_config; - ASSERT_TRUE(converter.GetAVCDecoderConfigurationRecord(&decoder_config)); + ASSERT_TRUE(converter.GetDecoderConfigurationRecord(&decoder_config)); EXPECT_EQ(expected_decoder_config, decoder_config); } @@ -56,7 +56,7 @@ TEST(H264ByteToUnitStreamConverter, ConversionFailure) { input_frame.size(), &output_frame)); std::vector decoder_config; - EXPECT_FALSE(converter.GetAVCDecoderConfigurationRecord(&decoder_config)); + EXPECT_FALSE(converter.GetDecoderConfigurationRecord(&decoder_config)); } } // namespace media diff --git a/packager/media/filters/h265_byte_to_unit_stream_converter.cc b/packager/media/filters/h265_byte_to_unit_stream_converter.cc new file mode 100644 index 0000000000..db9a2c7785 --- /dev/null +++ b/packager/media/filters/h265_byte_to_unit_stream_converter.cc @@ -0,0 +1,120 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/filters/h265_byte_to_unit_stream_converter.h" + +#include + +#include "packager/base/logging.h" +#include "packager/media/base/buffer_writer.h" +#include "packager/media/base/rcheck.h" +#include "packager/media/filters/h265_parser.h" + +namespace edash_packager { +namespace media { + +H265ByteToUnitStreamConverter::H265ByteToUnitStreamConverter() + : H26xByteToUnitStreamConverter(NaluReader::kH265) {} +H265ByteToUnitStreamConverter::~H265ByteToUnitStreamConverter() {} + +bool H265ByteToUnitStreamConverter::GetDecoderConfigurationRecord( + std::vector* decoder_config) const { + DCHECK(decoder_config); + + if (last_sps_.empty() || last_pps_.empty() || last_vps_.empty()) { + // No data available to construct HEVCDecoderConfigurationRecord. + return false; + } + + // We need to parse the SPS to get the data to add to the record. + int id; + Nalu nalu; + H265Parser parser; + RCHECK(nalu.InitializeFromH265(last_sps_.data(), last_sps_.size())); + RCHECK(parser.ParseSps(nalu, &id) == H265Parser::kOk); + const H265Sps* sps = parser.GetSps(id); + + // Construct an HEVCDecoderConfigurationRecord containing a single SPS, PPS, + // and VPS NALU. Please refer to ISO/IEC 14496-15 for format specifics. + BufferWriter buffer(last_sps_.size() + last_pps_.size() + last_vps_.size() + + 100); + buffer.AppendInt(static_cast(1) /* version */); + // (1) general_profile_space, general_tier_flag, general_profile_idc + // (4) general_profile_compatibility_flags + // (6) general_constraint_indicator_flags + // (1) general_level_idc + // Skip Nalu header (2) and the first byte of the SPS to get the + // profile_tier_level. + buffer.AppendArray(&last_sps_[2+1], 12); + // min_spacial_segmentation_idc = 0 (Unknown) + // TODO(modmaker): Parse vui_parameters and update this. + buffer.AppendInt(static_cast(0xf000)); + buffer.AppendInt(static_cast(0xfc) /* parallelismType = 0 */); + buffer.AppendInt(static_cast(0xfc | sps->chroma_format_idc)); + buffer.AppendInt(static_cast(0xf8 | sps->bit_depth_luma_minus8)); + buffer.AppendInt(static_cast(0xf8 | sps->bit_depth_chroma_minus8)); + buffer.AppendInt(static_cast(0) /* avgFrameRate */); + // Following flags are 0: + // constantFrameRate + // numTemporalLayers + // temporalIdNested + buffer.AppendInt(static_cast(kUnitStreamNaluLengthSize - 1)); + buffer.AppendInt(static_cast(3) /* numOfArrays */); + + // SPS + const uint8_t kArrayCompleteness = 0x80; + buffer.AppendInt(static_cast(kArrayCompleteness | Nalu::H265_SPS)); + buffer.AppendInt(static_cast(1) /* numNalus */); + buffer.AppendInt(static_cast(last_sps_.size())); + buffer.AppendVector(last_sps_); + + // PPS + buffer.AppendInt(static_cast(kArrayCompleteness | Nalu::H265_PPS)); + buffer.AppendInt(static_cast(1) /* numNalus */); + buffer.AppendInt(static_cast(last_pps_.size())); + buffer.AppendVector(last_pps_); + + // VPS + buffer.AppendInt(static_cast(kArrayCompleteness | Nalu::H265_VPS)); + buffer.AppendInt(static_cast(1) /* numNalus */); + buffer.AppendInt(static_cast(last_vps_.size())); + buffer.AppendVector(last_vps_); + + buffer.SwapBuffer(decoder_config); + return true; +} + +bool H265ByteToUnitStreamConverter::ProcessNalu(const Nalu& nalu) { + DCHECK(nalu.data()); + + // Skip the start code, but keep the 2-byte NALU header. + const uint8_t* nalu_ptr = nalu.data(); + const uint64_t nalu_size = nalu.payload_size() + nalu.header_size(); + + switch (nalu.type()) { + case Nalu::H265_SPS: + // Grab SPS NALU. + last_sps_.assign(nalu_ptr, nalu_ptr + nalu_size); + return true; + case Nalu::H265_PPS: + // Grab PPS NALU. + last_pps_.assign(nalu_ptr, nalu_ptr + nalu_size); + return true; + case Nalu::H265_VPS: + // Grab VPS NALU. + last_vps_.assign(nalu_ptr, nalu_ptr + nalu_size); + return true; + case Nalu::H265_AUD: + // Ignore AUD NALU. + return true; + default: + // Have the base class handle other NALU types. + return false; + } +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/filters/h265_byte_to_unit_stream_converter.h b/packager/media/filters/h265_byte_to_unit_stream_converter.h new file mode 100644 index 0000000000..8aab2d595f --- /dev/null +++ b/packager/media/filters/h265_byte_to_unit_stream_converter.h @@ -0,0 +1,46 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_FILTERS_H265_BYTE_TO_UNIT_STREAM_CONVERTER_H_ +#define MEDIA_FILTERS_H265_BYTE_TO_UNIT_STREAM_CONVERTER_H_ + +#include +#include + +#include + +#include "packager/media/filters/h26x_byte_to_unit_stream_converter.h" + +namespace edash_packager { +namespace media { + +/// Class which converts H.265 byte streams (as specified in ISO/IEC 14496-10 +/// Annex B) into H.265 NAL unit streams (as specified in ISO/IEC 14496-15). +class H265ByteToUnitStreamConverter : public H26xByteToUnitStreamConverter { + public: + H265ByteToUnitStreamConverter(); + ~H265ByteToUnitStreamConverter() override; + + /// @name H26xByteToUnitStreamConverter implementation override. + /// @{ + bool GetDecoderConfigurationRecord( + std::vector* decoder_config) const override; + /// @} + + private: + bool ProcessNalu(const Nalu& nalu) override; + + std::vector last_sps_; + std::vector last_pps_; + std::vector last_vps_; + + DISALLOW_COPY_AND_ASSIGN(H265ByteToUnitStreamConverter); +}; + +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_FILTERS_H265_BYTE_TO_UNIT_STREAM_CONVERTER_H_ diff --git a/packager/media/filters/h265_byte_to_unit_stream_converter_unittest.cc b/packager/media/filters/h265_byte_to_unit_stream_converter_unittest.cc new file mode 100644 index 0000000000..a1fcd13f09 --- /dev/null +++ b/packager/media/filters/h265_byte_to_unit_stream_converter_unittest.cc @@ -0,0 +1,75 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include +#include + +#include "packager/base/strings/string_number_conversions.h" +#include "packager/media/filters/h265_byte_to_unit_stream_converter.h" +#include "packager/media/filters/hevc_decoder_configuration.h" +#include "packager/media/test/test_data_util.h" + +namespace { +const char kExpectedConfigRecord[] = + "01016000000300900000030000f000fcfdf8f800000303a10001002e42010101600000" + "030090000003000003005da0028080241f265999a4932bffc0d5c0d640400000030040" + "00000602a2000100074401c172b46240a00001001840010c01ffff0160000003009000" + "0003000003005d999809"; +} + +namespace edash_packager { +namespace media { + +TEST(H265ByteToUnitStreamConverter, ConversionSuccess) { + 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"); + ASSERT_FALSE(expected_output_frame.empty()); + + H265ByteToUnitStreamConverter converter; + std::vector output_frame; + ASSERT_TRUE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), + input_frame.size(), + &output_frame)); + EXPECT_EQ(expected_output_frame, output_frame); + + std::vector expected_decoder_config; + ASSERT_TRUE(base::HexStringToBytes(kExpectedConfigRecord, + &expected_decoder_config)); + std::vector decoder_config; + ASSERT_TRUE(converter.GetDecoderConfigurationRecord(&decoder_config)); + EXPECT_EQ(expected_decoder_config, decoder_config); + + // Double-check that it can be parsed. + HEVCDecoderConfiguration conf; + ASSERT_TRUE(conf.Parse(decoder_config)); + // The order is SPS, PPS, VPS. + ASSERT_EQ(3u, conf.nalu_count()); + EXPECT_EQ(Nalu::H265_SPS, conf.nalu(0).type()); + EXPECT_EQ(Nalu::H265_PPS, conf.nalu(1).type()); + EXPECT_EQ(Nalu::H265_VPS, conf.nalu(2).type()); +} + +TEST(H265ByteToUnitStreamConverter, ConversionFailure) { + std::vector input_frame(100, 0); + + H265ByteToUnitStreamConverter converter; + std::vector output_frame; + EXPECT_FALSE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), + 0, + &output_frame)); + EXPECT_FALSE(converter.ConvertByteStreamToNalUnitStream(input_frame.data(), + input_frame.size(), + &output_frame)); + std::vector decoder_config; + EXPECT_FALSE(converter.GetDecoderConfigurationRecord(&decoder_config)); +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/filters/h26x_byte_to_unit_stream_converter.cc b/packager/media/filters/h26x_byte_to_unit_stream_converter.cc new file mode 100644 index 0000000000..38c39db074 --- /dev/null +++ b/packager/media/filters/h26x_byte_to_unit_stream_converter.cc @@ -0,0 +1,62 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/filters/h26x_byte_to_unit_stream_converter.h" + +#include + +#include "packager/base/logging.h" +#include "packager/media/base/buffer_writer.h" + +namespace edash_packager { +namespace media { + +namespace { +// Additional space to reserve for output frame. This value ought to be enough +// to acommodate frames consisting of 100 NAL units with 3-byte start codes. +const size_t kStreamConversionOverhead = 100; +} + +H26xByteToUnitStreamConverter::H26xByteToUnitStreamConverter( + NaluReader::CodecType type) + : type_(type) {} +H26xByteToUnitStreamConverter::~H26xByteToUnitStreamConverter() {} + +bool H26xByteToUnitStreamConverter::ConvertByteStreamToNalUnitStream( + const uint8_t* input_frame, + size_t input_frame_size, + std::vector* output_frame) { + DCHECK(input_frame); + DCHECK(output_frame); + + BufferWriter output_buffer(input_frame_size + kStreamConversionOverhead); + + Nalu nalu; + NaluReader reader(type_, kIsAnnexbByteStream, input_frame, input_frame_size); + if (!reader.StartsWithStartCode()) { + LOG(ERROR) << "H.26x byte stream frame did not begin with start code."; + return false; + } + + while (reader.Advance(&nalu) == NaluReader::kOk) { + const uint64_t nalu_size = nalu.payload_size() + nalu.header_size(); + DCHECK_LE(nalu_size, std::numeric_limits::max()); + + if (ProcessNalu(nalu)) + continue; + + // Append 4-byte length and NAL unit data to the buffer. + output_buffer.AppendInt(static_cast(nalu_size)); + output_buffer.AppendArray(nalu.data(), nalu_size); + } + + output_buffer.SwapBuffer(output_frame); + return true; +} + +} // namespace media +} // namespace edash_packager + diff --git a/packager/media/filters/h26x_byte_to_unit_stream_converter.h b/packager/media/filters/h26x_byte_to_unit_stream_converter.h new file mode 100644 index 0000000000..3cbc0c6306 --- /dev/null +++ b/packager/media/filters/h26x_byte_to_unit_stream_converter.h @@ -0,0 +1,64 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_FILTERS_H26X_BYTE_TO_UNIT_STREAM_CONVERTER_H_ +#define MEDIA_FILTERS_H26X_BYTE_TO_UNIT_STREAM_CONVERTER_H_ + +#include + +#include + +#include "packager/media/filters/nalu_reader.h" + +namespace edash_packager { +namespace media { + +class BufferWriter; + +/// A base class that is used to convert H.26x byte streams to NAL unit streams. +class H26xByteToUnitStreamConverter { + public: + static const size_t kUnitStreamNaluLengthSize = 4; + + H26xByteToUnitStreamConverter(NaluReader::CodecType type); + virtual ~H26xByteToUnitStreamConverter(); + + /// Converts a whole byte stream encoded video frame to NAL unit stream + /// format. + /// @param input_frame is a buffer containing a whole H.26x frame in byte + /// stream format. + /// @param input_frame_size is the size of the H.26x frame, in bytes. + /// @param output_frame is a pointer to a vector which will receive the + /// converted frame. + /// @return true if successful, false otherwise. + bool ConvertByteStreamToNalUnitStream(const uint8_t* input_frame, + size_t input_frame_size, + std::vector* output_frame); + + /// Creates either an AVCDecoderConfigurationRecord or a + /// HEVCDecoderConfigurationRecord from the units extracted from the byte + /// stream. + /// @param decoder_config is a pointer to a vector, which on successful + /// return will contain the computed record. + /// @return true if successful, or false otherwise. + virtual bool GetDecoderConfigurationRecord( + std::vector* decoder_config) const = 0; + + 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; + + NaluReader::CodecType type_; + + DISALLOW_COPY_AND_ASSIGN(H26xByteToUnitStreamConverter); +}; + +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_FILTERS_H26x_BYTE_TO_UNIT_STREAM_CONVERTER_H_ + diff --git a/packager/media/filters/nal_unit_to_byte_stream_converter.cc b/packager/media/filters/nal_unit_to_byte_stream_converter.cc index 355747373f..06009d5fbb 100644 --- a/packager/media/filters/nal_unit_to_byte_stream_converter.cc +++ b/packager/media/filters/nal_unit_to_byte_stream_converter.cc @@ -155,7 +155,7 @@ bool NalUnitToByteStreamConverter::ConvertUnitToByteStream( if (is_key_frame) buffer_writer.AppendVector(decoder_configuration_in_byte_stream_); - NaluReader nalu_reader(NaluReader::NaluType::kH264, nalu_length_size_, sample, + NaluReader nalu_reader(NaluReader::kH264, nalu_length_size_, sample, sample_size); Nalu nalu; NaluReader::Result result = nalu_reader.Advance(&nalu); diff --git a/packager/media/filters/nalu_reader.cc b/packager/media/filters/nalu_reader.cc index 5272cf44e7..bfb50f20ba 100644 --- a/packager/media/filters/nalu_reader.cc +++ b/packager/media/filters/nalu_reader.cc @@ -142,7 +142,7 @@ bool Nalu::InitializeFromH265(const uint8_t* data, uint64_t size) { return true; } -NaluReader::NaluReader(NaluType type, +NaluReader::NaluReader(CodecType type, uint8_t nal_length_size, const uint8_t* stream, uint64_t stream_size) diff --git a/packager/media/filters/nalu_reader.h b/packager/media/filters/nalu_reader.h index 9f526544f2..ac3b0ac001 100644 --- a/packager/media/filters/nalu_reader.h +++ b/packager/media/filters/nalu_reader.h @@ -125,7 +125,7 @@ class NaluReader { kInvalidStream, // error in stream kEOStream, // end of stream }; - enum NaluType { + enum CodecType { kH264, kH265, }; @@ -133,7 +133,7 @@ class NaluReader { /// @param nalu_length_size should be set to 0 for AnnexB byte streams; /// otherwise, it indicates the size of NAL unit length for the NAL /// unit stream. - NaluReader(NaluType type, + NaluReader(CodecType type, uint8_t nal_length_size, const uint8_t* stream, uint64_t stream_size); @@ -183,7 +183,7 @@ class NaluReader { // The remaining size of the stream. uint64_t stream_size_; // The type of NALU being read. - NaluType nalu_type_; + CodecType nalu_type_; // The number of bytes the prefix length is; only valid if format is // kAnnexbByteStreamFormat. uint8_t nalu_length_size_; diff --git a/packager/media/formats/mp2t/es_parser_h264.cc b/packager/media/formats/mp2t/es_parser_h264.cc index aae88d542f..c6d59c3fef 100644 --- a/packager/media/formats/mp2t/es_parser_h264.cc +++ b/packager/media/formats/mp2t/es_parser_h264.cc @@ -329,7 +329,7 @@ bool EsParserH264::EmitFrame(int64_t access_unit_pos, bool EsParserH264::UpdateVideoDecoderConfig(const H264Sps* sps) { std::vector decoder_config_record; - if (!stream_converter_->GetAVCDecoderConfigurationRecord( + if (!stream_converter_->GetDecoderConfigurationRecord( &decoder_config_record)) { DLOG(ERROR) << "Failure to construct an AVCDecoderConfigurationRecord"; return false; diff --git a/packager/media/formats/mp4/encrypting_fragmenter.cc b/packager/media/formats/mp4/encrypting_fragmenter.cc index 7de34f9f04..d28d08f205 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.cc +++ b/packager/media/formats/mp4/encrypting_fragmenter.cc @@ -238,7 +238,7 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { data += frame.frame_size; } } else { - const NaluReader::NaluType nalu_type = + const NaluReader::CodecType nalu_type = (video_codec_ == kCodecHVC1 || video_codec_ == kCodecHEV1) ? NaluReader::kH265 : NaluReader::kH264; diff --git a/packager/media/formats/wvm/wvm_media_parser.cc b/packager/media/formats/wvm/wvm_media_parser.cc index 32d9ea7aeb..2aa02e9dfb 100644 --- a/packager/media/formats/wvm/wvm_media_parser.cc +++ b/packager/media/formats/wvm/wvm_media_parser.cc @@ -827,7 +827,7 @@ bool WvmMediaParser::Output(bool output_encrypted_sample) { // Set extra data for video stream from AVC Decoder Config Record. // Also, set codec string from the AVC Decoder Config Record. std::vector decoder_config_record; - byte_to_unit_stream_converter_.GetAVCDecoderConfigurationRecord( + byte_to_unit_stream_converter_.GetDecoderConfigurationRecord( &decoder_config_record); for (uint32_t i = 0; i < stream_infos_.size(); i++) { if (stream_infos_[i]->stream_type() == media::kStreamVideo && diff --git a/packager/media/test/data/README b/packager/media/test/data/README index 007ae3fe60..682241bca9 100644 --- a/packager/media/test/data/README +++ b/packager/media/test/data/README @@ -87,3 +87,6 @@ bear_no_i_frame_start.h264 avc-byte-stream-frame.h264 - Single IDR frame extracted from test-25fps.h264 in Annex B byte stream format. avc-unit-stream-frame.h264 - Single IDR frame from avc-byte-stream-frame.h264 converted to unit stream format. + +hevc-byte-stream-frame.h265 - Several video frames with SPS/PPS/VPS manually extracted from an H.265 stream, in Annex B byte stream format. +hevc-byte-stream-frame.h265 - hevc-byte-stream-frame.h265 converted to unit stream format. diff --git a/packager/media/test/data/hevc-byte-stream-frame.h265 b/packager/media/test/data/hevc-byte-stream-frame.h265 new file mode 100644 index 0000000000000000000000000000000000000000..623ae99049890db50dd2634b0d1a0619c754e72d GIT binary patch literal 11403 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 Nycz