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

#include "media/formats/mp4/avc.h"

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <array>
#include <memory>
#include <optional>
#include <ostream>
#include <string_view>

#include "base/containers/span.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/test/scoped_feature_list.h"
#include "media/base/decrypt_config.h"
#include "media/base/media_switches.h"
#include "media/base/stream_parser_buffer.h"
#include "media/formats/mp4/bitstream_converter.h"
#include "media/formats/mp4/box_definitions.h"
#include "media/formats/mp4/nalu_test_helper.h"
#include "media/parsers/h264_parser.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media::mp4 {

static constexpr auto kNALU1 = std::to_array<uint8_t>({0x01, 0x02, 0x03});
static constexpr auto kNALU2 = std::to_array<uint8_t>({0x04, 0x05, 0x06, 0x07});
static constexpr auto kExpected =
    std::to_array<uint8_t>({0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03, 0x00,
                            0x00, 0x00, 0x01, 0x04, 0x05, 0x06, 0x07});

static constexpr auto kExpectedParamSets = std::to_array<uint8_t>(
    {0x00, 0x00, 0x00, 0x01, 0x67, 0x12, 0x00, 0x00, 0x00, 0x01, 0x67, 0x34,
     0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78});

static std::string_view NALUTypeToString(int type) {
  switch (type) {
    case H264NALU::kNonIDRSlice:
      return "P";
    case H264NALU::kSliceDataA:
      return "SDA";
    case H264NALU::kSliceDataB:
      return "SDB";
    case H264NALU::kSliceDataC:
      return "SDC";
    case H264NALU::kIDRSlice:
      return "I";
    case H264NALU::kSEIMessage:
      return "SEI";
    case H264NALU::kSPS:
      return "SPS";
    case H264NALU::kSPSExt:
      return "SPSExt";
    case H264NALU::kPPS:
      return "PPS";
    case H264NALU::kAUD:
      return "AUD";
    case H264NALU::kEOSeq:
      return "EOSeq";
    case H264NALU::kEOStream:
      return "EOStr";
    case H264NALU::kFiller:
      return "FILL";
    case H264NALU::kPrefix:
      return "Prefix";
    case H264NALU::kSubsetSPS:
      return "SubsetSPS";
    case H264NALU::kDPS:
      return "DPS";

    case H264NALU::kUnspecified:
    case H264NALU::kReserved17:
    case H264NALU::kReserved18:
    case H264NALU::kCodedSliceAux:
    case H264NALU::kCodedSliceExtension:
      NOTREACHED() << "Unexpected type: " << type;
  };

  return "UnsupportedType";
}

// Helper output operator, for debugging/testability.
std::ostream& operator<<(std::ostream& os,
                         const BitstreamConverter::AnalysisResult& r) {
  os << "{ is_conformant: "
     << (r.is_conformant.has_value()
             ? (r.is_conformant.value() ? "true" : "false")
             : "nullopt/unknown")
     << ", is_keyframe: "
     << (r.is_keyframe.has_value() ? (r.is_keyframe.value() ? "true" : "false")
                                   : "nullopt/unknown")
     << " }";
  return os;
}

static std::string AnnexBToString(
    const std::vector<uint8_t>& buffer,
    const std::vector<SubsampleEntry>& subsamples) {
  std::stringstream ss;

  H264Parser parser;
  parser.SetEncryptedStream(buffer, subsamples);

  H264NALU nalu;
  bool first = true;
  size_t current_subsample_index = 0;
  while (parser.AdvanceToNextNALU(&nalu) == H264Parser::kOk) {
    size_t subsample_index =
        AVC::FindSubsampleIndex(buffer, subsamples, nalu.data.data());
    if (!first) {
      ss << (subsample_index == current_subsample_index ? "," : " ");
    } else {
      DCHECK_EQ(subsample_index, current_subsample_index);
      first = false;
    }

    ss << NALUTypeToString(nalu.nal_unit_type);
    current_subsample_index = subsample_index;
  }
  return ss.str();
}

class AVCConversionTest : public testing::TestWithParam<int> {
 protected:
  void WriteLength(int length_size, int length, std::vector<uint8_t>* buf) {
    DCHECK_GE(length, 0);
    DCHECK_LE(length, 255);

    for (int i = 1; i < length_size; i++)
      buf->push_back(0);
    buf->push_back(length);
  }

  void MakeInputForLength(int length_size, std::vector<uint8_t>* buf) {
    buf->clear();

    WriteLength(length_size, sizeof(kNALU1), buf);
    buf->insert(buf->end(), kNALU1.begin(), kNALU1.end());

    WriteLength(length_size, sizeof(kNALU2), buf);
    buf->insert(buf->end(), kNALU2.begin(), kNALU2.end());
  }

};

TEST_P(AVCConversionTest, ParseCorrectly) {
  std::vector<uint8_t> buf;
  std::vector<SubsampleEntry> subsamples;
  MakeInputForLength(GetParam(), &buf);
  EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf, &subsamples));

  BitstreamConverter::AnalysisResult expected;
  expected.is_conformant = true;
  expected.is_keyframe = false;
  EXPECT_PRED2(AnalysesMatch, AVC::AnalyzeAnnexB(buf, subsamples), expected);

  EXPECT_EQ(buf.size(), sizeof(kExpected));
  EXPECT_EQ(kExpected, base::as_byte_span(buf));
  EXPECT_EQ("P,SDC", AnnexBToString(buf, subsamples));
}

// Intentionally write NALU sizes that are larger than the buffer.
TEST_P(AVCConversionTest, NALUSizeTooLarge) {
  std::vector<uint8_t> buf;
  WriteLength(GetParam(), 10 * sizeof(kNALU1), &buf);
  buf.insert(buf.end(), kNALU1.begin(), kNALU1.end());
  EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf, nullptr));
}

TEST_P(AVCConversionTest, NALUSizeIsZero) {
  std::vector<uint8_t> buf;
  WriteLength(GetParam(), 0, &buf);

  WriteLength(GetParam(), sizeof(kNALU1), &buf);
  buf.insert(buf.end(), kNALU1.begin(), kNALU1.end());

  WriteLength(GetParam(), 0, &buf);

  WriteLength(GetParam(), sizeof(kNALU2), &buf);
  buf.insert(buf.end(), kNALU2.begin(), kNALU2.end());

  EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf, nullptr));
}

TEST_P(AVCConversionTest, SubsampleSizesUpdatedAfterAnnexBConversion) {
  std::vector<uint8_t> buf;
  std::vector<SubsampleEntry> subsamples;
  SubsampleEntry subsample;

  // Write the first subsample, consisting of only one NALU
  WriteLength(GetParam(), sizeof(kNALU1), &buf);
  buf.insert(buf.end(), kNALU1.begin(), kNALU1.end());

  subsample.clear_bytes = GetParam() + sizeof(kNALU1);
  subsample.cypher_bytes = 0;
  subsamples.push_back(subsample);

  // Write the second subsample, containing two NALUs
  WriteLength(GetParam(), sizeof(kNALU1), &buf);
  buf.insert(buf.end(), kNALU1.begin(), kNALU1.end());
  WriteLength(GetParam(), sizeof(kNALU2), &buf);
  buf.insert(buf.end(), kNALU2.begin(), kNALU2.end());

  subsample.clear_bytes = 2*GetParam() + sizeof(kNALU1) + sizeof(kNALU2);
  subsample.cypher_bytes = 0;
  subsamples.push_back(subsample);

  // Write the third subsample, containing a single one-byte NALU
  WriteLength(GetParam(), 1, &buf);
  buf.push_back(0);
  subsample.clear_bytes = GetParam() + 1;
  subsample.cypher_bytes = 0;
  subsamples.push_back(subsample);

  EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf, &subsamples));
  EXPECT_EQ(subsamples.size(), 3u);
  EXPECT_EQ(subsamples[0].clear_bytes, 4 + sizeof(kNALU1));
  EXPECT_EQ(subsamples[0].cypher_bytes, 0u);
  EXPECT_EQ(subsamples[1].clear_bytes, 8 + sizeof(kNALU1) + sizeof(kNALU2));
  EXPECT_EQ(subsamples[1].cypher_bytes, 0u);
  EXPECT_EQ(subsamples[2].clear_bytes, 4 + 1u);
  EXPECT_EQ(subsamples[2].cypher_bytes, 0u);
}

TEST_P(AVCConversionTest, ParsePartial) {
  std::vector<uint8_t> buf;
  MakeInputForLength(GetParam(), &buf);
  buf.pop_back();
  EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf, nullptr));
  // This tests a buffer ending in the middle of a NAL length. For length size
  // of one, this can't happen, so we skip that case.
  if (GetParam() != 1) {
    MakeInputForLength(GetParam(), &buf);
    buf.erase(buf.end() - (sizeof(kNALU2) + 1), buf.end());
    EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf, nullptr));
  }
}

TEST_P(AVCConversionTest, ParseEmpty) {
  std::vector<uint8_t> buf;
  EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf, nullptr));
  EXPECT_EQ(0u, buf.size());
}

INSTANTIATE_TEST_SUITE_P(AVCConversionTestValues,
                         AVCConversionTest,
                         ::testing::Values(1, 2, 4));

TEST_F(AVCConversionTest, AnalyzeSEI) {
  base::test::ScopedFeatureList scoped_sei_flag(kParseSEIRecoveryPoints);
  constexpr auto kStream = std::to_array<const uint8_t>({
      // First NALU Start code.
      0x00,
      0x00,
      0x00,
      0x01,
      // NALU type = 6 (kSEIMessage).
      0x06,
      // SEI payload type = 6 (recovery_point).
      0x06,
      // SEI payload size = 1.
      0x01,
      // SEI payload.
      0x84,
      // RBSP trailing bits.
      0x80,
      // Second NALU Start code.
      0x00,
      0x00,
      0x00,
      0x01,
      // NALU type = 6 (kSEIMessage).
      0x06,
      // SEI payload type = 1 (pic_timing).
      0x01,
      // SEI payload size = 1.
      0x01,
      // SEI payload.
      0x04,
      // RBSP trailing bits.
      0x80,
  });

  auto result = AVC::AnalyzeAnnexB(kStream, {});
  EXPECT_TRUE(result.is_conformant);
  EXPECT_FALSE(result.is_keyframe.has_value());
  EXPECT_TRUE(result.is_sei_recovery_point.has_value());
  EXPECT_TRUE(result.is_sei_recovery_point.value());
}

TEST_F(AVCConversionTest, AnalyzeSEICorruptionNonFatal) {
  base::test::ScopedFeatureList scoped_sei_flag(kParseSEIRecoveryPoints);
  constexpr auto kStream = std::to_array<const uint8_t>({
      // First NALU Start code.
      0x00,
      0x00,
      0x00,
      0x01,
      // NALU type = 6 (kSEIMessage).
      0x06,
      // SEI payload type = 6 (recovery_point).
      0x06,
      // SEI payload size = 255 to trigger an error.
      0xFF,
      // SEI payload.
      0x84,
      // RBSP trailing bits.
      0x80,
  });

  auto result = AVC::AnalyzeAnnexB(kStream, {});
  EXPECT_TRUE(result.is_conformant);
  EXPECT_FALSE(result.is_keyframe.has_value());
  EXPECT_FALSE(result.is_sei_recovery_point.has_value());
}

TEST_F(AVCConversionTest, ConvertConfigToAnnexB) {
  AVCDecoderConfigurationRecord avc_config;
  avc_config.sps_list.resize(2);
  avc_config.sps_list[0].push_back(0x67);
  avc_config.sps_list[0].push_back(0x12);
  avc_config.sps_list[1].push_back(0x67);
  avc_config.sps_list[1].push_back(0x34);
  avc_config.pps_list.resize(1);
  avc_config.pps_list[0].push_back(0x68);
  avc_config.pps_list[0].push_back(0x56);
  avc_config.pps_list[0].push_back(0x78);

  std::vector<uint8_t> buf;
  std::vector<SubsampleEntry> subsamples;
  EXPECT_TRUE(AVC::ConvertConfigToAnnexB(avc_config, &buf));
  EXPECT_EQ(kExpectedParamSets, base::as_byte_span(buf));
  EXPECT_EQ("SPS,SPS,PPS", AnnexBToString(buf, subsamples));
}

// Verify that we can round trip string -> Annex B -> string.
TEST_F(AVCConversionTest, StringConversionFunctions) {
  std::string str =
      "AUD SPS SPSExt SPS PPS SEI SEI Prefix I P FILL EOSeq EOStr";
  std::vector<uint8_t> buf;
  std::vector<SubsampleEntry> subsamples;
  AvcStringToAnnexB(str, &buf, &subsamples);

  BitstreamConverter::AnalysisResult expected;
  expected.is_conformant = true;
  expected.is_keyframe = true;
  EXPECT_PRED2(AnalysesMatch, AVC::AnalyzeAnnexB(buf, subsamples), expected);

  EXPECT_EQ(str, AnnexBToString(buf, subsamples));
}

TEST_F(AVCConversionTest, ReservedNalUnitsIgnored) {
  std::string str = "FILL I EOStr";
  std::vector<uint8_t> buf;
  std::vector<SubsampleEntry> subsamples;
  AvcStringToAnnexB(str, &buf, &subsamples);
  buf[4] = 25;  // Change FILL NALU type to reserved type.

  BitstreamConverter::AnalysisResult expected;
  expected.is_conformant = false;
  expected.is_keyframe = true;
  EXPECT_PRED2(AnalysesMatch, AVC::AnalyzeAnnexB(buf, subsamples), expected);
}

TEST_F(AVCConversionTest, ValidAnnexBConstructs) {
  base::test::ScopedFeatureList scoped_feature_list(
      kH264IDRKeyframeRequiresParameterSets);

  struct TestCases {
    const char* case_string;
    const bool is_keyframe;
    const bool allow_bare_idr = true;
  };
  auto test_cases = std::to_array<TestCases>({
      {"I", true},
      {"I I I I", true},
      {"AUD I", true},
      {"AUD SPS PPS I", true},
      {"I EOSeq", true},
      {"I EOSeq EOStr", true},
      {"I EOStr", true},
      {"P", false},
      {"P P P P", false},
      {"AUD SPS PPS P", false},
      {"SEI SEI I", true},
      {"SEI SEI Prefix I", true},
      {"SPS SPSExt SPS PPS I P", true},
      {"Prefix SEI I", true},
      {"AUD,I", true},
      {"AUD,SEI I", true},
      {"AUD,SEI,SPS,PPS,I", true},

      // In reality, these might not always be conformant/valid, but assuming
      // they are, they're not keyframes because a non-IDR slice preceded the
      // IDR slice, if any.
      {"SDA SDB SDC", false},
      {"P I", false},
      {"SDA I", false},
      {"SDB I", false},
      {"SDC I", false},

      // Verify IDR keyframe detection when param sets are not supplied upfront
      // (e.g., avc3 with no SPS/PPS in avc config) and must appear in-band.
      {"I", false, false},         // Bare IDR without param sets in config
      {"SPS PPS I", true, false},  // In-band SPS+PPS before IDR -> keyframe
  });

  for (size_t i = 0; i < std::size(test_cases); ++i) {
    std::vector<uint8_t> buf;
    std::vector<SubsampleEntry> subsamples;
    AvcStringToAnnexB(test_cases[i].case_string, &buf, nullptr);

    BitstreamConverter::AnalysisResult expected;
    expected.is_conformant = true;
    expected.is_keyframe = test_cases[i].is_keyframe;
    EXPECT_PRED2(
        AnalysesMatch,
        AVC::AnalyzeAnnexB(buf, subsamples, test_cases[i].allow_bare_idr),
        expected)
        << "'" << test_cases[i].case_string << "' failed "
        << "(allow_bare_idr=" << test_cases[i].allow_bare_idr << ")";
  }
}

TEST_F(AVCConversionTest, EmptyBuffer) {
  std::vector<SubsampleEntry> subsamples;
  auto result = AVC::AnalyzeAnnexB(base::span<const uint8_t>(), subsamples);
  EXPECT_TRUE(result.is_conformant);
  EXPECT_TRUE(subsamples.empty());
  EXPECT_FALSE(result.is_keyframe.has_value());
}

TEST_F(AVCConversionTest, InvalidAnnexBConstructs) {
  struct TestCases {
    const char* case_string;
    const std::optional<bool> is_keyframe;
  };
  auto test_cases = std::to_array<TestCases>({
      // For these cases, lack of conformance is determined before detecting any
      // IDR or non-IDR slices, so the non-conformant frames' keyframe analysis
      // reports std::nullopt (which means undetermined analysis result).
      {"AUD", std::nullopt},        // No VCL present.
      {"AUD,SEI", std::nullopt},    // No VCL present.
      {"SPS PPS", std::nullopt},    // No VCL present.
      {"SPS PPS AUD I", true},      // Parameter sets must come after AUD.
      {"SPSExt SPS P", false},      // SPS must come before SPSExt.
      {"SPS PPS SPSExt P", false},  // SPSExt must follow an SPS.
      {"EOSeq", std::nullopt},      // EOSeq must come after a VCL.
      {"EOStr", std::nullopt},      // EOStr must come after a VCL.

      // For these cases, IDR slice is first VCL and is detected before
      // conformance failure, so the non-conformant frame is reported as a
      // keyframe.
      {"I EOStr EOSeq", true},  // EOSeq must come before EOStr.
      {"I Prefix", true},       // Reserved14-18 must come before first VCL.
      {"I SEI", true},          // SEI must come before first VCL.
      {"SEI AUD I", true},      // AUD must be first NALU.

      // For this case, P slice is first VCL and is detected before conformance
      // failure, so the non-conformant frame is reported as a non-keyframe.
      {"P SPS P",
       false},  // SPS after first VCL would indicate a new access unit.
  });

  BitstreamConverter::AnalysisResult expected;
  expected.is_conformant = false;

  for (size_t i = 0; i < std::size(test_cases); ++i) {
    std::vector<uint8_t> buf;
    std::vector<SubsampleEntry> subsamples;
    AvcStringToAnnexB(test_cases[i].case_string, &buf, nullptr);
    expected.is_keyframe = test_cases[i].is_keyframe;
    EXPECT_PRED2(AnalysesMatch, AVC::AnalyzeAnnexB(buf, subsamples), expected)
        << "'" << test_cases[i].case_string << "' failed";
  }
}

typedef struct {
  const char* input;
  const char* expected;
} InsertTestCases;

TEST_F(AVCConversionTest, InsertParamSetsAnnexB) {
  static const InsertTestCases test_cases[] = {
    { "I", "SPS,SPS,PPS,I" },
    { "AUD I", "AUD SPS,SPS,PPS,I" },

    // Cases where param sets in |avc_config| are placed before
    // the existing ones.
    { "SPS,PPS,I", "SPS,SPS,PPS,SPS,PPS,I" },
    { "AUD,SPS,PPS,I", "AUD,SPS,SPS,PPS,SPS,PPS,I" },  // Note: params placed
                                                       // after AUD.

    // One or more NALUs might follow AUD in the first subsample, we need to
    // handle this correctly. Params should be inserted right after AUD.
    { "AUD,SEI I", "AUD,SPS,SPS,PPS,SEI I" },
  };

  AVCDecoderConfigurationRecord avc_config;
  avc_config.sps_list.resize(2);
  avc_config.sps_list[0].push_back(0x67);
  avc_config.sps_list[0].push_back(0x12);
  avc_config.sps_list[1].push_back(0x67);
  avc_config.sps_list[1].push_back(0x34);
  avc_config.pps_list.resize(1);
  avc_config.pps_list[0].push_back(0x68);
  avc_config.pps_list[0].push_back(0x56);
  avc_config.pps_list[0].push_back(0x78);

  BitstreamConverter::AnalysisResult expected;
  expected.is_conformant = true;
  expected.is_keyframe = true;

  for (auto test_case : test_cases) {
    std::vector<uint8_t> buf;
    std::vector<SubsampleEntry> subsamples;

    AvcStringToAnnexB(test_case.input, &buf, &subsamples);

    EXPECT_TRUE(AVC::InsertParamSetsAnnexB(avc_config, &buf, &subsamples))
        << "'" << test_case.input << "' insert failed.";
    EXPECT_PRED2(AnalysesMatch, AVC::AnalyzeAnnexB(buf, subsamples), expected)
        << "'" << test_case.input << "' created invalid AnnexB.";
    EXPECT_EQ(test_case.expected, AnnexBToString(buf, subsamples))
        << "'" << test_case.input << "' generated unexpected output.";
  }
}

// Verify that SPS/PPS injection works for SEI recovery point frames.
// This tests the behavior that ConvertAndAnalyzeFrame uses: when
// is_sei_recovery_point is true, SPS/PPS should be injected so the hardware
// decoder can initialize after a seek/reset.
TEST_F(AVCConversionTest, InsertParamSetsForRecoveryPointFrame) {
  base::test::ScopedFeatureList scoped_sei_flag(kParseSEIRecoveryPoints);

  AVCDecoderConfigurationRecord avc_config;
  avc_config.sps_list.resize(1);
  avc_config.sps_list[0].push_back(0x67);
  avc_config.sps_list[0].push_back(0x12);
  avc_config.pps_list.resize(1);
  avc_config.pps_list[0].push_back(0x68);
  avc_config.pps_list[0].push_back(0x56);
  avc_config.pps_list[0].push_back(0x78);

  // Build an Annex B frame with SEI recovery point + non-IDR slice.
  std::vector<uint8_t> buf = {
      // Start code + SEI NALU.
      0x00,
      0x00,
      0x00,
      0x01,
      0x06,
      0x06,
      0x01,
      0x84,
      0x80,
      // Start code + non-IDR slice.
      0x00,
      0x00,
      0x00,
      0x01,
      0x01,
      0x32,
      0x12,
  };
  std::vector<SubsampleEntry> subsamples;

  // Analyze first to confirm it's a recovery point.
  auto analysis = AVC::AnalyzeAnnexB(buf, subsamples);
  EXPECT_TRUE(analysis.is_sei_recovery_point.value_or(false));
  EXPECT_FALSE(analysis.is_keyframe.value_or(true));

  // Insert SPS/PPS (as ConvertAndAnalyzeFrame does for recovery points).
  EXPECT_TRUE(AVC::InsertParamSetsAnnexB(avc_config, &buf, &subsamples));

  // Verify SPS and PPS were injected before the SEI.
  std::string annexb_str = AnnexBToString(buf, subsamples);
  EXPECT_NE(annexb_str.find("SPS"), std::string::npos)
      << "SPS not found in output: " << annexb_str;
  EXPECT_NE(annexb_str.find("PPS"), std::string::npos)
      << "PPS not found in output: " << annexb_str;
}

// Verify that a regular non-IDR frame (no SEI recovery point) is not
// identified as a recovery point and would not trigger SPS/PPS injection.
TEST_F(AVCConversionTest, RegularNonIDRNotRecoveryPoint) {
  base::test::ScopedFeatureList scoped_sei_flag(kParseSEIRecoveryPoints);

  constexpr auto kNonIDRFrame = std::to_array<const uint8_t>({
      // Start code + non-IDR slice.
      0x00,
      0x00,
      0x00,
      0x01,
      0x01,
      0x32,
      0x12,
  });

  auto result = AVC::AnalyzeAnnexB(kNonIDRFrame, {});
  EXPECT_TRUE(result.is_conformant);
  EXPECT_TRUE(result.is_keyframe.has_value());
  EXPECT_FALSE(result.is_keyframe.value());
  // No SEI recovery point — SPS/PPS injection should NOT happen.
  EXPECT_FALSE(result.is_sei_recovery_point.has_value());
}

// Builds an MP4 length-prefixed (length_size=4) AVC frame containing an SEI
// recovery point NALU followed by a non-IDR slice NALU. This mirrors the
// bitstream shape that triggers the SEI-recovery branch in
// AVCBitstreamConverter::ConvertAndAnalyzeFrame.
static std::vector<uint8_t> MakeLengthPrefixedSEIRecoveryFrame() {
  return {
      // 4-byte length = 5, then SEI NALU (type=6, payload type=6
      // recovery_point, payload size=1, payload=0x84, RBSP trailing=0x80).
      0x00,
      0x00,
      0x00,
      0x05,
      0x06,
      0x06,
      0x01,
      0x84,
      0x80,
      // 4-byte length = 3, then non-IDR slice NALU (type=1, data).
      0x00,
      0x00,
      0x00,
      0x03,
      0x01,
      0x32,
      0x12,
  };
}

// Length-prefixed non-IDR frame without SEI recovery point. Used to verify
// that ConvertAndAnalyzeFrame does NOT inject SPS/PPS for regular frames.
static std::vector<uint8_t> MakeLengthPrefixedNonIDRFrame() {
  return {
      // 4-byte length = 3, then non-IDR slice NALU (type=1, data).
      0x00, 0x00, 0x00, 0x03, 0x01, 0x32, 0x12,
  };
}

static std::unique_ptr<AVCDecoderConfigurationRecord>
MakeAvcConfigWithSpsPps() {
  auto config = std::make_unique<AVCDecoderConfigurationRecord>();
  config->length_size = 4;
  config->sps_list.resize(1);
  config->sps_list[0].push_back(0x67);
  config->sps_list[0].push_back(0x12);
  config->pps_list.resize(1);
  config->pps_list[0].push_back(0x68);
  config->pps_list[0].push_back(0x56);
  config->pps_list[0].push_back(0x78);
  return config;
}

// Clear content: ConvertAndAnalyzeFrame should inject SPS/PPS for a non-IDR
// frame carrying an SEI recovery point, so the HW decoder can initialize
// after a seek/reset.
TEST_F(AVCConversionTest,
       ConvertAndAnalyzeFrameInjectsParamSetsForSEIRecoveryInClearContent) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {kParseSEIRecoveryPoints, kMediaSourceSeiRecoveryPointKeyframe}, {});

  auto converter =
      base::MakeRefCounted<AVCBitstreamConverter>(MakeAvcConfigWithSpsPps());
  std::vector<uint8_t> buf = MakeLengthPrefixedSEIRecoveryFrame();
  std::vector<SubsampleEntry> subsamples;  // empty = clear content.
  BitstreamConverter::AnalysisResult analysis;

  EXPECT_TRUE(converter->ConvertAndAnalyzeFrame(&buf, /*is_keyframe=*/false,
                                                &subsamples, &analysis));
  EXPECT_TRUE(analysis.is_sei_recovery_point.value_or(false));

  const std::string annexb_str = AnnexBToString(buf, subsamples);
  EXPECT_NE(annexb_str.find("SPS"), std::string::npos)
      << "Expected SPS injection in clear content: " << annexb_str;
  EXPECT_NE(annexb_str.find("PPS"), std::string::npos)
      << "Expected PPS injection in clear content: " << annexb_str;
}

// Regular non-IDR frame (no SEI recovery point): ConvertAndAnalyzeFrame must
// NOT inject SPS/PPS. Only keyframes and SEI recovery point frames get
// parameter set injection.
TEST_F(AVCConversionTest,
       ConvertAndAnalyzeFrameSkipsParamSetsForRegularNonIDRFrame) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {kParseSEIRecoveryPoints, kMediaSourceSeiRecoveryPointKeyframe}, {});

  auto converter =
      base::MakeRefCounted<AVCBitstreamConverter>(MakeAvcConfigWithSpsPps());
  std::vector<uint8_t> buf = MakeLengthPrefixedNonIDRFrame();
  std::vector<SubsampleEntry> subsamples;
  BitstreamConverter::AnalysisResult analysis;

  EXPECT_TRUE(converter->ConvertAndAnalyzeFrame(&buf, /*is_keyframe=*/false,
                                                &subsamples, &analysis));
  EXPECT_FALSE(analysis.is_sei_recovery_point.value_or(false));

  const std::string annexb_str = AnnexBToString(buf, subsamples);
  EXPECT_EQ(annexb_str.find("SPS"), std::string::npos)
      << "SPS must not be injected for regular non-IDR frame: " << annexb_str;
  EXPECT_EQ(annexb_str.find("PPS"), std::string::npos)
      << "PPS must not be injected for regular non-IDR frame: " << annexb_str;
}

// Encrypted content: ConvertAndAnalyzeFrame must NOT inject SPS/PPS for an
// SEI recovery point frame. Some older Intel/AMD HW decoders mishandle
// SEI + SPS/PPS and encrypted streams lack the software decode fallback
// needed to recover. See https://crbug.com/451536366.
TEST_F(AVCConversionTest,
       ConvertAndAnalyzeFrameSkipsParamSetsForSEIRecoveryInEncryptedContent) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {kParseSEIRecoveryPoints, kMediaSourceSeiRecoveryPointKeyframe}, {});

  auto converter =
      base::MakeRefCounted<AVCBitstreamConverter>(MakeAvcConfigWithSpsPps());
  std::vector<uint8_t> buf = MakeLengthPrefixedSEIRecoveryFrame();
  // Non-empty subsamples signals encrypted content. Entries must align with
  // NAL boundaries so H264Parser can still parse the clear headers.
  std::vector<SubsampleEntry> subsamples;
  subsamples.emplace_back(/*clear_bytes=*/5u, /*cypher_bytes=*/4u);
  subsamples.emplace_back(/*clear_bytes=*/5u, /*cypher_bytes=*/2u);
  BitstreamConverter::AnalysisResult analysis;

  EXPECT_TRUE(converter->ConvertAndAnalyzeFrame(&buf, /*is_keyframe=*/false,
                                                &subsamples, &analysis));
  EXPECT_TRUE(analysis.is_sei_recovery_point.value_or(false));

  const std::string annexb_str = AnnexBToString(buf, subsamples);
  EXPECT_EQ(annexb_str.find("SPS"), std::string::npos)
      << "SPS must not be injected for encrypted SEI recovery: " << annexb_str;
  EXPECT_EQ(annexb_str.find("PPS"), std::string::npos)
      << "PPS must not be injected for encrypted SEI recovery: " << annexb_str;
}

// Flag kill-switch: when kMediaSourceSeiRecoveryPointKeyframe is disabled,
// ConvertAndAnalyzeFrame must NOT inject SPS/PPS for an SEI recovery point
// frame even on clear content. This mirrors the gate in mp4_stream_parser.cc
// so a Finch rollback fully disables the new bitstream modification.
TEST_F(AVCConversionTest,
       ConvertAndAnalyzeFrameSkipsParamSetsForSEIRecoveryWhenFlagDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures({kParseSEIRecoveryPoints},
                                       {kMediaSourceSeiRecoveryPointKeyframe});

  auto converter =
      base::MakeRefCounted<AVCBitstreamConverter>(MakeAvcConfigWithSpsPps());
  std::vector<uint8_t> buf = MakeLengthPrefixedSEIRecoveryFrame();
  std::vector<SubsampleEntry> subsamples;  // empty = clear content.
  BitstreamConverter::AnalysisResult analysis;

  EXPECT_TRUE(converter->ConvertAndAnalyzeFrame(&buf, /*is_keyframe=*/false,
                                                &subsamples, &analysis));
  EXPECT_TRUE(analysis.is_sei_recovery_point.value_or(false));

  const std::string annexb_str = AnnexBToString(buf, subsamples);
  EXPECT_EQ(annexb_str.find("SPS"), std::string::npos)
      << "SPS must not be injected when flag is disabled: " << annexb_str;
  EXPECT_EQ(annexb_str.find("PPS"), std::string::npos)
      << "PPS must not be injected when flag is disabled: " << annexb_str;
}

}  // namespace media::mp4
