From 9bb6c5d8d2666ce632e4b19d330c4d8e681801a9 Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Wed, 4 May 2016 14:19:40 -0700 Subject: [PATCH] Add more unit tests for EsParserH26x. This add several unit tests for EsParserH26x to test the various NAL unit orders and access unit contents. Some tests are disabled and will be fixed in another patch. Change-Id: Id5e3291e22f1fe17ada2c03c42e2cdfe226abcb2 --- packager/media/filters/nalu_reader.h | 2 + packager/media/formats/mp2t/es_parser_h264.cc | 6 +- packager/media/formats/mp2t/es_parser_h265.cc | 8 +- packager/media/formats/mp2t/es_parser_h26x.cc | 26 +- packager/media/formats/mp2t/es_parser_h26x.h | 1 + .../formats/mp2t/es_parser_h26x_unittest.cc | 301 ++++++++++++++++++ packager/media/formats/mp2t/mp2t.gyp | 1 + 7 files changed, 323 insertions(+), 22 deletions(-) create mode 100644 packager/media/formats/mp2t/es_parser_h26x_unittest.cc diff --git a/packager/media/filters/nalu_reader.h b/packager/media/filters/nalu_reader.h index 454677aa4c..2345499926 100644 --- a/packager/media/filters/nalu_reader.h +++ b/packager/media/filters/nalu_reader.h @@ -46,6 +46,7 @@ class Nalu { }; enum H265NaluType { H265_TRAIL_N = 0, + H265_TRAIL_R = 1, H265_TSA_N = 2, H265_TSA_R = 3, H265_STSA_N = 4, @@ -71,6 +72,7 @@ class Nalu { H265_EOS = 36, H265_EOB = 37, + H265_FD = 38, H265_PREFIX_SEI = 39, diff --git a/packager/media/formats/mp2t/es_parser_h264.cc b/packager/media/formats/mp2t/es_parser_h264.cc index a34f298a3a..d8014c946e 100644 --- a/packager/media/formats/mp2t/es_parser_h264.cc +++ b/packager/media/formats/mp2t/es_parser_h264.cc @@ -22,7 +22,11 @@ namespace mp2t { EsParserH264::EsParserH264(uint32_t pid, const NewStreamInfoCB& new_stream_info_cb, const EmitSampleCB& emit_sample_cb) - : EsParserH26x(Nalu::kH264, pid, emit_sample_cb), + : EsParserH26x(Nalu::kH264, + scoped_ptr( + new H264ByteToUnitStreamConverter()), + pid, + emit_sample_cb), new_stream_info_cb_(new_stream_info_cb), decoder_config_check_pending_(false), h264_parser_(new H264Parser()) {} diff --git a/packager/media/formats/mp2t/es_parser_h265.cc b/packager/media/formats/mp2t/es_parser_h265.cc index 985231889a..173c0210fb 100644 --- a/packager/media/formats/mp2t/es_parser_h265.cc +++ b/packager/media/formats/mp2t/es_parser_h265.cc @@ -14,8 +14,8 @@ #include "packager/media/base/timestamp.h" #include "packager/media/base/video_stream_info.h" #include "packager/media/filters/hevc_decoder_configuration.h" +#include "packager/media/filters/h265_byte_to_unit_stream_converter.h" #include "packager/media/filters/h265_parser.h" -#include "packager/media/filters/h26x_byte_to_unit_stream_converter.h" #include "packager/media/formats/mp2t/mp2t_common.h" namespace edash_packager { @@ -25,7 +25,11 @@ namespace mp2t { EsParserH265::EsParserH265(uint32_t pid, const NewStreamInfoCB& new_stream_info_cb, const EmitSampleCB& emit_sample_cb) - : EsParserH26x(Nalu::kH265, pid, emit_sample_cb), + : EsParserH26x(Nalu::kH265, + scoped_ptr( + new H265ByteToUnitStreamConverter()), + pid, + emit_sample_cb), new_stream_info_cb_(new_stream_info_cb), decoder_config_check_pending_(false), h265_parser_(new H265Parser()) {} diff --git a/packager/media/formats/mp2t/es_parser_h26x.cc b/packager/media/formats/mp2t/es_parser_h26x.cc index dcc24d28b9..707c181bc9 100644 --- a/packager/media/formats/mp2t/es_parser_h26x.cc +++ b/packager/media/formats/mp2t/es_parser_h26x.cc @@ -20,32 +20,20 @@ namespace edash_packager { namespace media { namespace mp2t { -namespace { - -H26xByteToUnitStreamConverter* CreateStreamConverter(Nalu::CodecType type) { - if (type == Nalu::kH264) { - return new H264ByteToUnitStreamConverter(); - } else { - DCHECK_EQ(Nalu::kH265, type); - return new H265ByteToUnitStreamConverter(); - } -} - -} // anonymous namespace - -EsParserH26x::EsParserH26x(Nalu::CodecType type, - uint32_t pid, - const EmitSampleCB& emit_sample_cb) +EsParserH26x::EsParserH26x( + Nalu::CodecType type, + scoped_ptr stream_converter, + uint32_t pid, + const EmitSampleCB& emit_sample_cb) : EsParser(pid), emit_sample_cb_(emit_sample_cb), type_(type), es_queue_(new media::OffsetByteQueue()), current_access_unit_pos_(0), found_access_unit_(false), - stream_converter_(CreateStreamConverter(type)), + stream_converter_(stream_converter.Pass()), pending_sample_duration_(0), - waiting_for_key_frame_(true) { -} + waiting_for_key_frame_(true) {} EsParserH26x::~EsParserH26x() {} diff --git a/packager/media/formats/mp2t/es_parser_h26x.h b/packager/media/formats/mp2t/es_parser_h26x.h index 4edff45d33..7279d3ffa0 100644 --- a/packager/media/formats/mp2t/es_parser_h26x.h +++ b/packager/media/formats/mp2t/es_parser_h26x.h @@ -27,6 +27,7 @@ namespace mp2t { class EsParserH26x : public EsParser { public: EsParserH26x(Nalu::CodecType type, + scoped_ptr stream_converter, uint32_t pid, const EmitSampleCB& emit_sample_cb); ~EsParserH26x() override; diff --git a/packager/media/formats/mp2t/es_parser_h26x_unittest.cc b/packager/media/formats/mp2t/es_parser_h26x_unittest.cc new file mode 100644 index 0000000000..adbf8c84c5 --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_h26x_unittest.cc @@ -0,0 +1,301 @@ +// 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/bind.h" +#include "packager/base/logging.h" +#include "packager/media/base/media_sample.h" +#include "packager/media/base/stream_info.h" +#include "packager/media/base/timestamp.h" +#include "packager/media/filters/h26x_byte_to_unit_stream_converter.h" +#include "packager/media/formats/mp2t/es_parser_h26x.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +namespace { + +// NAL unit types used for testing. +enum H265NaluType { + kAud = Nalu::H265_AUD, + kSps = Nalu::H265_SPS, + kSei = Nalu::H265_PREFIX_SEI, + // Something with |can_start_access_unit() == false|. + kRsv = Nalu::H265_FD, + // Non-key-frame video slice. + kVcl = Nalu::H265_TRAIL_N, + kVclKeyFrame = Nalu::H265_IDR_W_RADL, + // Needs to be different than |kVCL| so we can tell the difference. + kVclWithNuhLayer = Nalu::H265_TRAIL_R, + // Used to separate expected access units. + kSeparator = 0xff, +}; + +class FakeByteToUnitStreamConverter : public H26xByteToUnitStreamConverter { + public: + FakeByteToUnitStreamConverter() + : H26xByteToUnitStreamConverter(Nalu::kH265) {} + + bool GetDecoderConfigurationRecord( + std::vector* decoder_config) const override { + return true; + } + + bool ProcessNalu(const Nalu& nalu) override { + // This processed nothing, base class should copy everything. + return false; + } +}; + +// This is the code-under-test. This implements the required abstract methods +// to ignore the contents of the NAL units. This behaves the same as the +// H.264 and H.265 types. +class TestableEsParser : public EsParserH26x { + public: + TestableEsParser(const NewStreamInfoCB& new_stream_info_cb, + const EmitSampleCB& emit_sample_cb) + : EsParserH26x(Nalu::kH265, + scoped_ptr( + new FakeByteToUnitStreamConverter()), + 0, + emit_sample_cb), + new_stream_info_cb_(new_stream_info_cb), + decoder_config_check_pending_(false) {} + + bool ProcessNalu(const Nalu& nalu, + bool* is_key_frame, + int* pps_id_for_access_unit) override { + if (nalu.type() == Nalu::H265_SPS) { + decoder_config_check_pending_ = true; + } else if (nalu.is_video_slice()) { + // This should be the same as EsParserH265::ProcessNalu. + *is_key_frame = nalu.type() == Nalu::H265_IDR_W_RADL || + nalu.type() == Nalu::H265_IDR_N_LP; + *pps_id_for_access_unit = kTestPpsId; + } + return true; + } + + bool UpdateVideoDecoderConfig(int pps_id) override { + if (decoder_config_check_pending_) { + EXPECT_EQ(kTestPpsId, pps_id); + new_stream_info_cb_.Run(nullptr); + decoder_config_check_pending_ = false; + } + return true; + } + + private: + const int kTestPpsId = 123; + + NewStreamInfoCB new_stream_info_cb_; + bool decoder_config_check_pending_; +}; + +std::vector CreateNalu(H265NaluType type, int i) { + std::vector ret; + ret.resize(4); + ret[0] = (type << 1); + // nuh_layer_id == 1, nuh_temporal_id_plus1 == 1 + ret[1] = (type == kVclWithNuhLayer ? 9 : 1); + // Add some extra data to tell consecutive frames apart. + ret[2] = 0xff; + ret[3] = i + 1; + return ret; +} + +} // namespace + +class EsParserH26xTest : public testing::Test { + public: + EsParserH26xTest() : sample_count_(0), has_stream_info_(false) {} + + // Runs a test by constructing NAL units of the given types and passing them + // to the parser. Access units should be separated by |kSeparator|, there + // should be one at the start and not at the end. + void RunTest(const H265NaluType* types, size_t types_count); + + void EmitSample(uint32_t pid, const scoped_refptr& sample) { + size_t sample_id = sample_count_; + sample_count_++; + if (sample_count_ == 1) + EXPECT_TRUE(sample->is_key_frame()); + + ASSERT_GT(samples_.size(), sample_id); + const std::vector sample_data( + sample->data(), sample->data() + sample->data_size()); + EXPECT_EQ(samples_[sample_id], sample_data); + } + + void NewVideoConfig(const scoped_refptr& config) { + has_stream_info_ = true; + } + + protected: + std::vector> samples_; + size_t sample_count_; + bool has_stream_info_; +}; + +void EsParserH26xTest::RunTest(const H265NaluType* types, + size_t types_count) { + // Duration of one 25fps video frame in 90KHz clock units. + const uint32_t kMpegTicksPerFrame = 3600; + const uint8_t kStartCode[] = {0x00, 0x00, 0x01}; + + TestableEsParser es_parser( + base::Bind(&EsParserH26xTest::NewVideoConfig, base::Unretained(this)), + base::Bind(&EsParserH26xTest::EmitSample, base::Unretained(this))); + + bool seen_key_frame = false; + std::vector cur_sample_data; + ASSERT_EQ(kSeparator, types[0]); + for (size_t k = 1; k < types_count; k++) { + if (types[k] == kSeparator) { + // We should not be emitting samples until we see a key frame. + if (seen_key_frame) + samples_.push_back(cur_sample_data); + cur_sample_data.clear(); + } else { + if (types[k] == kVclKeyFrame) + seen_key_frame = true; + + std::vector es_data = CreateNalu(types[k], k); + cur_sample_data.push_back(0); + cur_sample_data.push_back(0); + cur_sample_data.push_back(0); + cur_sample_data.push_back(es_data.size()); + cur_sample_data.insert(cur_sample_data.end(), es_data.begin(), + es_data.end()); + es_data.insert(es_data.begin(), kStartCode, + kStartCode + arraysize(kStartCode)); + + const int64_t pts = k * kMpegTicksPerFrame; + const int64_t dts = k * kMpegTicksPerFrame; + // This may process the previous sample; but since we don't know whether + // we are at the end yet, this will not process the current sample until + // later. + ASSERT_TRUE(es_parser.Parse(es_data.data(), es_data.size(), pts, dts)); + } + } + if (seen_key_frame) + samples_.push_back(cur_sample_data); + + es_parser.Flush(); +} + +TEST_F(EsParserH26xTest, BasicSupport) { + const H265NaluType kData[] = { + kSeparator, kAud, kSps, kVclKeyFrame, + kSeparator, kAud, kVcl, + kSeparator, kAud, kVcl, + }; + + RunTest(kData, arraysize(kData)); + EXPECT_EQ(3u, sample_count_); + EXPECT_TRUE(has_stream_info_); +} + +TEST_F(EsParserH26xTest, DeterminesAccessUnitsWithoutAUD) { + const H265NaluType kData[] = { + kSeparator, kSps, kVclKeyFrame, + kSeparator, kVcl, + kSeparator, kVcl, + kSeparator, kSei, kVcl, + }; + + RunTest(kData, arraysize(kData)); + EXPECT_EQ(4u, sample_count_); + EXPECT_TRUE(has_stream_info_); +} + +TEST_F(EsParserH26xTest, DoesNotStartOnRsv) { + const H265NaluType kData[] = { + kSeparator, kSps, kVclKeyFrame, kRsv, + kSeparator, kAud, kVcl, + kSeparator, kSei, kVcl, + }; + + RunTest(kData, arraysize(kData)); + EXPECT_EQ(3u, sample_count_); + EXPECT_TRUE(has_stream_info_); +} + +TEST_F(EsParserH26xTest, DISABLED_SupportsNonZeroNuhLayerId) { + const H265NaluType kData[] = { + kSeparator, kSps, kVclKeyFrame, + kSeparator, kAud, kVcl, kSei, kSei, kVclWithNuhLayer, kRsv, + kSeparator, kSei, kVcl, + kSeparator, kAud, kVcl, kSps, kRsv, kVclWithNuhLayer, + kSeparator, kVcl, + }; + + RunTest(kData, arraysize(kData)); + EXPECT_EQ(5u, sample_count_); + EXPECT_TRUE(has_stream_info_); +} + +TEST_F(EsParserH26xTest, WaitsForKeyFrame) { + const H265NaluType kData[] = { + kSeparator, kVcl, + kSeparator, kVcl, + kSeparator, kSps, kVclKeyFrame, + kSeparator, kVcl, + kSeparator, kVcl, + }; + + RunTest(kData, arraysize(kData)); + EXPECT_EQ(3u, sample_count_); + EXPECT_TRUE(has_stream_info_); +} + +TEST_F(EsParserH26xTest, EmitsFramesWithNoStreamInfo) { + const H265NaluType kData[] = { + kSeparator, kVclKeyFrame, + kSeparator, kVcl, + kSeparator, kVcl, + }; + + RunTest(kData, arraysize(kData)); + EXPECT_EQ(3u, sample_count_); + EXPECT_FALSE(has_stream_info_); +} + +// TODO(modmaker): Currently, the SEI here will not be included. This needs to +// be fixed. +TEST_F(EsParserH26xTest, DISABLED_EmitsLastFrameWhenDoesntEndOnVCL) { + // This tests that it will emit the last frame and last frame will include + // the correct data and nothing extra. + const H265NaluType kData[] = { + kSeparator, kVclKeyFrame, + kSeparator, kVcl, + kSeparator, kVcl, kSei, + }; + + RunTest(kData, arraysize(kData)); + EXPECT_EQ(3u, sample_count_); + EXPECT_FALSE(has_stream_info_); +} + +TEST_F(EsParserH26xTest, DISABLED_EmitsLastFrameWithNuhLayerId) { + const H265NaluType kData[] = { + kSeparator, kVclKeyFrame, + kSeparator, kVcl, + kSeparator, kVcl, kVclWithNuhLayer, kSei, + }; + + RunTest(kData, arraysize(kData)); + EXPECT_EQ(3u, sample_count_); + EXPECT_FALSE(has_stream_info_); +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/mp2t.gyp b/packager/media/formats/mp2t/mp2t.gyp index 9a4df07541..d5e6debb47 100644 --- a/packager/media/formats/mp2t/mp2t.gyp +++ b/packager/media/formats/mp2t/mp2t.gyp @@ -61,6 +61,7 @@ 'sources': [ 'adts_header_unittest.cc', 'es_parser_h264_unittest.cc', + 'es_parser_h26x_unittest.cc', 'mp2t_media_parser_unittest.cc', 'pes_packet_generator_unittest.cc', 'ts_segmenter_unittest.cc',