chromium/media/parsers/h266_nalu_parser_unittest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>

#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "media/base/subsample_entry.h"
#include "media/base/test_data_util.h"
#include "media/parsers/h266_nalu_parser.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

namespace {
struct VvcTestData {
  std::string file_name;
  // Number of NALUs in the test stream to be parsed.
  int num_nalus;
};

}  // namespace

class H266NaluParserTest : public ::testing::Test {
 protected:
  void LoadParserFile(std::string file_name) {
    parser_.Reset();
    base::FilePath file_path = GetTestDataFilePath(file_name);
    stream_ = std::make_unique<base::MemoryMappedFile>();
    ASSERT_TRUE(stream_->Initialize(file_path))
        << "Couldn't open stream file: " << file_path.MaybeAsASCII();
    parser_.SetStream(stream_->data(), stream_->length());
  }
  bool ParseNalusUntilNut(H266NALU* target_nalu, H266NALU::Type nalu_type) {
    while (true) {
      H266NaluParser::Result res = parser_.AdvanceToNextNALU(target_nalu);
      if (res == H266NaluParser::kEndOfStream) {
        return false;
      }
      EXPECT_EQ(res, H266NaluParser::kOk);
      if (target_nalu->nal_unit_type == nalu_type) {
        return true;
      }
    }
  }
  H266NaluParser parser_;
  std::unique_ptr<base::MemoryMappedFile> stream_;
};

TEST_F(H266NaluParserTest, RawVvcStreamFileParsingShouldSucceed) {
  VvcTestData test_data[] = {
      {"bear_180p.vvc", 54},
      {"bbb_360p.vvc", 87},
  };
  for (const auto& data : test_data) {
    LoadParserFile(data.file_name);
    // Parse until the end of stream/unsupported stream/error in stream is
    // found.
    int num_parsed_nalus = 0;
    while (true) {
      H266NALU nalu;
      H266NaluParser::Result res = parser_.AdvanceToNextNALU(&nalu);
      if (res == H266NaluParser::kEndOfStream) {
        DVLOG(1) << "Number of successfully parsed NALUs before EOS: "
                 << num_parsed_nalus;
        EXPECT_EQ(data.num_nalus, num_parsed_nalus);
        break;
      }
      EXPECT_EQ(res, H266NaluParser::kOk);
      ++num_parsed_nalus;
      DVLOG(4) << "Found NALU " << nalu.nal_unit_type;
      EXPECT_EQ(res, H266NaluParser::kOk);
    }
  }
}

// Verify that GetCurrentSubsamples works.
TEST_F(H266NaluParserTest, GetCurrentSubsamplesNormalShouldSucceed) {
  constexpr uint8_t kStream[] = {
      // First NALU.
      // Clear bytes = 5.
      0x00,
      0x00,
      0x01,  // start code.
      0x00,
      0x41,  // Nalu type = 8, IDR_N_LP picture
      // Below is bogus data.
      // Encrypted bytes = 15.
      0x00,
      0x01,
      0x02,
      0x03,
      0x04,
      0x05,
      0x06,
      0x07,
      0x00,
      0x01,
      0x02,
      0x03,
      0x04,
      0x05,
      0x06,
      // Clear bytes = 5.
      0x07,
      0x00,
      0x01,
      0x02,
      0x03,
      // Encrypted until next NALU. Encrypted bytes = 20.
      0x04,
      0x05,
      0x06,
      0x07,
      0x00,
      0x01,
      0x02,
      0x03,
      0x04,
      0x05,
      0x06,
      0x07,
      // Note that this is still in the encrypted region but looks like a start
      // code.
      0x00,
      0x00,
      0x01,
      0x03,
      0x04,
      0x05,
      0x06,
      0x07,
      // Second NALU. Completely clear.
      // Clear bytes = 11.
      0x00,
      0x00,
      0x01,  // start code.
      0x00,
      0x79,  // nalu type = 15, SPS.
      // Bogus data.
      0xff,
      0xfe,
      0xfd,
      0xee,
      0x12,
      0x33,
  };
  std::vector<SubsampleEntry> subsamples;
  subsamples.emplace_back(5u, 15u);
  subsamples.emplace_back(5u, 20u);
  subsamples.emplace_back(11u, 0u);
  H266NaluParser parser;
  parser.SetEncryptedStream(kStream, std::size(kStream), subsamples);
  H266NALU nalu;
  EXPECT_EQ(H266NaluParser::kOk, parser.AdvanceToNextNALU(&nalu));
  EXPECT_EQ(H266NALU::kIDRNoLeadingPicture, nalu.nal_unit_type);
  auto nalu_subsamples = parser.GetCurrentSubsamples();
  EXPECT_EQ(2u, nalu_subsamples.size());
  // Note that nalu->data starts from the NALU header, i.e. does not include
  // the start code.
  EXPECT_EQ(2u, nalu_subsamples[0].clear_bytes);
  EXPECT_EQ(15u, nalu_subsamples[0].cypher_bytes);
  EXPECT_EQ(5u, nalu_subsamples[1].clear_bytes);
  EXPECT_EQ(20u, nalu_subsamples[1].cypher_bytes);
  // Make sure that it reached the next NALU.
  EXPECT_EQ(H266NaluParser::kOk, parser.AdvanceToNextNALU(&nalu));
  EXPECT_EQ(H266NALU::kSPS, nalu.nal_unit_type);
  nalu_subsamples = parser.GetCurrentSubsamples();
  EXPECT_EQ(1u, nalu_subsamples.size());
  EXPECT_EQ(8u, nalu_subsamples[0].clear_bytes);
  EXPECT_EQ(0u, nalu_subsamples[0].cypher_bytes);
}

// Verify that subsamples starting at non-NALU boundary also works.
TEST_F(H266NaluParserTest,
       GetCurrentSubsamplesSubsampleNotStartingAtNaluBoundaryShouldSucceed) {
  constexpr uint8_t kStream[] = {
      // First NALU.
      // Clear bytes = 5.
      0x00,
      0x00,
      0x01,  // start code.
      0x00,
      0x41,  // Nalu type = 8, IDR_N_LP slice.
      // Below is bogus data.
      // Encrypted bytes = 24.
      0x00,
      0x01,
      0x02,
      0x03,
      0x04,
      0x05,
      0x06,
      0x07,
      0x00,
      0x01,
      0x02,
      0x03,
      0x04,
      0x05,
      0x06,
      0x07,
      0x00,
      0x01,
      0x02,
      0x03,
      0x04,
      0x05,
      0x06,
      0x07,
      // Clear bytes = 19. The rest is in the clear. Note that this is not at
      // a NALU boundary and a NALU starts below.
      0xaa,
      0x01,
      0x02,
      0x03,
      0x04,
      0x05,
      0x06,
      0x07,
      // Second NALU. Completely clear.
      0x00,
      0x00,
      0x01,  // start code.
      0x00,
      0x79,  // nalu type = 15, SPS.
      // Bogus data.
      0xff,
      0xfe,
      0xfd,
      0xee,
      0x12,
      0x33,
  };
  std::vector<SubsampleEntry> subsamples;
  subsamples.emplace_back(5u, 24u);
  subsamples.emplace_back(19u, 0u);
  H266NaluParser parser;
  parser.SetEncryptedStream(kStream, std::size(kStream), subsamples);
  H266NALU nalu;
  EXPECT_EQ(H266NaluParser::kOk, parser.AdvanceToNextNALU(&nalu));
  EXPECT_EQ(H266NALU::kIDRNoLeadingPicture, nalu.nal_unit_type);
  auto nalu_subsamples = parser.GetCurrentSubsamples();
  EXPECT_EQ(2u, nalu_subsamples.size());
  // Note that nalu->data starts from the NALU header, i.e. does not include
  // the start code.
  EXPECT_EQ(2u, nalu_subsamples[0].clear_bytes);
  EXPECT_EQ(24u, nalu_subsamples[0].cypher_bytes);
  // The nalu ends with 8 more clear bytes. The last 10 bytes should be
  // associated with the next nalu.
  EXPECT_EQ(8u, nalu_subsamples[1].clear_bytes);
  EXPECT_EQ(0u, nalu_subsamples[1].cypher_bytes);
  EXPECT_EQ(H266NaluParser::kOk, parser.AdvanceToNextNALU(&nalu));
  EXPECT_EQ(H266NALU::kSPS, nalu.nal_unit_type);
  nalu_subsamples = parser.GetCurrentSubsamples();
  EXPECT_EQ(1u, nalu_subsamples.size());
  // Although the input had 10 more bytes, since nalu->data starts from the nalu
  // header, there's only 7 more bytes left.
  EXPECT_EQ(8u, nalu_subsamples[0].clear_bytes);
  EXPECT_EQ(0u, nalu_subsamples[0].cypher_bytes);
}

// Verify that nalus with invalid nuh_layer_id should be ignored.
TEST_F(H266NaluParserTest, NaluWithOutOfRangeNuhLayerIdShouldBeIgnored) {
  constexpr uint8_t kStream[] = {0x00, 0x00,
                                 0x01,  // start code.
                                 0x56,
                                 0x41,  // Nalu type = 8, IDR_N_LP slice.
                                 // Below is bogus data.
                                 0x00, 0x01, 0x02, 0x03, 0x04};
  H266NaluParser parser;
  parser.SetStream(kStream, std::size(kStream));
  H266NALU nalu;
  EXPECT_EQ(H266NaluParser::kIgnored, parser.AdvanceToNextNALU(&nalu));
}

// Verify that nalus with invalid nuh_temproal_id_plus1 should not be allowed.
TEST_F(H266NaluParserTest, ParseNaluWithInvalidTemporalIdShouldFail) {
  constexpr uint8_t kStream[] = {
      0x00, 0x00,
      0x01,  // start code.
      0x00,
      0x00,  // Nalu type = 0, TRAIL_NUT slice, but nuh_temporal_id_plus1 is 0
      // Below is bogus data.
      0x50, 0x01, 0x02, 0x03, 0x04,
      // Second Nalu.
      0x00, 0x00,
      0x01,  // start code.
      0x00,
      0x42,  // Nalu type = 8, IDR_N_LP slice, but nuh_temporal_id_plus1 is 2
      // Below is bogus data.
      0x00, 0x01, 0x02, 0x03, 0x04};
  H266NaluParser parser;
  parser.SetStream(kStream, std::size(kStream));
  H266NALU nalu;
  // First Nalu with nuh_temporal_id_plus1 of 0 is not allowed.
  EXPECT_EQ(H266NaluParser::kInvalidStream, parser.AdvanceToNextNALU(&nalu));
  // IRAP picture should be with nuh_temporal_id_plus1 of 1.
  EXPECT_EQ(H266NaluParser::kInvalidStream, parser.AdvanceToNextNALU(&nalu));
}

// Verify that non-IRAP pictures with nuh_temporal_id_plus1 not equal to 1
// should succeed.
TEST_F(H266NaluParserTest, ParseNonIrapNaluWithNonZeroTemporalIdShouldSucceed) {
  constexpr uint8_t kStream[] = {
      0x00, 0x00,
      0x01,  // start code.
      0x00,
      0x0e,  // Nalu type = 0, TRAIL_NUT slice, and nuh_temporal_id_plus1 is 6
      // Below is bogus data.
      0x94, 0x55, 0x48, 0x03, 0x04};
  H266NaluParser parser;
  parser.SetStream(kStream, std::size(kStream));
  H266NALU nalu;
  EXPECT_EQ(H266NaluParser::kOk, parser.AdvanceToNextNALU(&nalu));
}

}  // namespace media