chromium/media/gpu/mac/video_toolbox_h265_accelerator_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 "media/gpu/mac/video_toolbox_h265_accelerator.h"

#include <memory>

#include "base/containers/span.h"
#include "media/base/media_util.h"
#include "media/gpu/codec_picture.h"
#include "media/parsers/h265_parser.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using testing::ElementsAre;
using testing::SaveArg;

namespace media {

namespace {

// Configuration from buck1080p60_hevc.mp4
constexpr uint8_t kVPS0[] = {0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60,
                             0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03,
                             0x00, 0x00, 0x03, 0x00, 0x7b, 0x95, 0x98, 0x09};
constexpr uint8_t kSPS0[] = {
    0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00,
    0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x7b, 0xa0, 0x03, 0xc0, 0x80,
    0x10, 0xe5, 0x96, 0x56, 0x69, 0x24, 0xca, 0xf0, 0x16, 0x9c, 0x20,
    0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x07, 0x81};
constexpr uint8_t kPPS0[] = {0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40};

// Configuration from bear-1280x720-hevc-10bit-hdr10.mp4
constexpr uint8_t kVPS1[] = {0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20,
                             0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03,
                             0x00, 0x00, 0x03, 0x00, 0x5d, 0x95, 0x98, 0x09};
constexpr uint8_t kSPS1[] = {
    0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00,
    0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x13,
    0x65, 0x95, 0x9a, 0x49, 0x32, 0xbc, 0x05, 0xa8, 0x48, 0x80, 0x4f, 0x08,
    0x00, 0x00, 0x1f, 0x48, 0x00, 0x03, 0xa9, 0x80, 0x40};
constexpr uint8_t kPPS1[] = {0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40};

constexpr uint8_t kSliceData[] = {0x02};

}  // namespace

class VideoToolboxH265AcceleratorTest : public testing::Test {
 public:
  VideoToolboxH265AcceleratorTest() = default;
  ~VideoToolboxH265AcceleratorTest() override = default;

 protected:
  MOCK_METHOD3(OnDecode,
               void(base::apple::ScopedCFTypeRef<CMSampleBufferRef>,
                    VideoToolboxDecompressionSessionMetadata,
                    scoped_refptr<CodecPicture>));
  MOCK_METHOD1(OnOutput, void(scoped_refptr<CodecPicture>));

  std::unique_ptr<VideoToolboxH265Accelerator> accelerator_{
      std::make_unique<VideoToolboxH265Accelerator>(
          std::make_unique<NullMediaLog>(),
          base::BindRepeating(&VideoToolboxH265AcceleratorTest::OnDecode,
                              base::Unretained(this)),
          base::BindRepeating(&VideoToolboxH265AcceleratorTest::OnOutput,
                              base::Unretained(this)))};
};

TEST_F(VideoToolboxH265AcceleratorTest, Construct) {}

TEST_F(VideoToolboxH265AcceleratorTest, DecodeOne) {
  scoped_refptr<H265Picture> pic = accelerator_->CreateH265Picture();
  H265VPS vps;
  H265SPS sps;
  H265PPS pps;
  H265SliceHeader slice_hdr;
  H265Picture::Vector ref_pic_list;
  std::vector<SubsampleEntry> subsamples;

  // Decode frame.
  accelerator_->ProcessVPS(&vps, base::make_span(kVPS0));
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, &slice_hdr, ref_pic_list,
                                    ref_pic_list, ref_pic_list, ref_pic_list,
                                    pic);
  accelerator_->SubmitSlice(&sps, &pps, &slice_hdr, ref_pic_list, ref_pic_list,
                            ref_pic_list, ref_pic_list, ref_pic_list, pic,
                            kSliceData, sizeof(kSliceData), subsamples);

  // Save the resulting sample.
  base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample;
  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<0>(&sample));
  accelerator_->SubmitDecode(pic);

  // Verify sample.
  CMBlockBufferRef buf = CMSampleBufferGetDataBuffer(sample.get());
  std::vector<uint8_t> data(CMBlockBufferGetDataLength(buf));
  CMBlockBufferCopyDataBytes(buf, 0, CMBlockBufferGetDataLength(buf),
                             data.data());
  EXPECT_THAT(data, ElementsAre(0x00, 0x00, 0x00, 0x01,  // length
                                0x02                     // kSliceData
                                ));

  // Check that OutputPicture() works.
  EXPECT_CALL(*this, OnOutput(_));
  accelerator_->OutputPicture(pic);
}

TEST_F(VideoToolboxH265AcceleratorTest, DecodeTwo) {
  scoped_refptr<H265Picture> pic0 = accelerator_->CreateH265Picture();
  scoped_refptr<H265Picture> pic1 = accelerator_->CreateH265Picture();
  H265VPS vps;
  H265SPS sps;
  H265PPS pps;
  H265SliceHeader slice_hdr;
  H265Picture::Vector ref_pic_list;
  std::vector<SubsampleEntry> subsamples;

  // First frame.
  accelerator_->ProcessVPS(&vps, base::make_span(kVPS0));
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, &slice_hdr, ref_pic_list,
                                    ref_pic_list, ref_pic_list, ref_pic_list,
                                    pic0);
  accelerator_->SubmitSlice(&sps, &pps, &slice_hdr, ref_pic_list, ref_pic_list,
                            ref_pic_list, ref_pic_list, ref_pic_list, pic0,
                            kSliceData, sizeof(kSliceData), subsamples);

  // Save the resulting sample.
  base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample0;
  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<0>(&sample0));
  accelerator_->SubmitDecode(pic0);

  // Second frame.
  accelerator_->ProcessVPS(&vps, base::make_span(kVPS0));
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, &slice_hdr, ref_pic_list,
                                    ref_pic_list, ref_pic_list, ref_pic_list,
                                    pic1);
  accelerator_->SubmitSlice(&sps, &pps, &slice_hdr, ref_pic_list, ref_pic_list,
                            ref_pic_list, ref_pic_list, ref_pic_list, pic1,
                            kSliceData, sizeof(kSliceData), subsamples);

  // Save the resulting sample.
  base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample1;
  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<0>(&sample1));
  accelerator_->SubmitDecode(pic1);

  // The two samples should have the same configuration.
  EXPECT_EQ(CMSampleBufferGetFormatDescription(sample0.get()),
            CMSampleBufferGetFormatDescription(sample1.get()));
}

TEST_F(VideoToolboxH265AcceleratorTest, DecodeTwo_Reset) {
  scoped_refptr<H265Picture> pic0 = accelerator_->CreateH265Picture();
  scoped_refptr<H265Picture> pic1 = accelerator_->CreateH265Picture();
  H265VPS vps;
  H265SPS sps;
  H265PPS pps;
  H265SliceHeader slice_hdr;
  H265Picture::Vector ref_pic_list;
  std::vector<SubsampleEntry> subsamples;

  // First frame.
  accelerator_->ProcessVPS(&vps, base::make_span(kVPS0));
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, &slice_hdr, ref_pic_list,
                                    ref_pic_list, ref_pic_list, ref_pic_list,
                                    pic0);
  accelerator_->SubmitSlice(&sps, &pps, &slice_hdr, ref_pic_list, ref_pic_list,
                            ref_pic_list, ref_pic_list, ref_pic_list, pic0,
                            kSliceData, sizeof(kSliceData), subsamples);

  // Save the resulting sample.
  base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample0;
  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<0>(&sample0));
  accelerator_->SubmitDecode(pic0);

  // Reset.
  accelerator_->Reset();

  // Second frame.
  accelerator_->ProcessVPS(&vps, base::make_span(kVPS0));
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, &slice_hdr, ref_pic_list,
                                    ref_pic_list, ref_pic_list, ref_pic_list,
                                    pic1);
  accelerator_->SubmitSlice(&sps, &pps, &slice_hdr, ref_pic_list, ref_pic_list,
                            ref_pic_list, ref_pic_list, ref_pic_list, pic1,
                            kSliceData, sizeof(kSliceData), subsamples);

  // Save the resulting sample.
  base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample1;
  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<0>(&sample1));
  accelerator_->SubmitDecode(pic1);

  // The accelerator should have made a new configuration. (Technically it
  // should be fine to reuse the old one because the parameter sets did not
  // change.)
  EXPECT_NE(CMSampleBufferGetFormatDescription(sample0.get()),
            CMSampleBufferGetFormatDescription(sample1.get()));
}

TEST_F(VideoToolboxH265AcceleratorTest, DecodeTwo_ConfigChange) {
  scoped_refptr<H265Picture> pic0 = accelerator_->CreateH265Picture();
  scoped_refptr<H265Picture> pic1 = accelerator_->CreateH265Picture();
  H265VPS vps;
  H265SPS sps;
  H265PPS pps;
  H265SliceHeader slice_hdr;
  H265Picture::Vector ref_pic_list;
  std::vector<SubsampleEntry> subsamples;

  // First frame.
  accelerator_->ProcessVPS(&vps, base::make_span(kVPS0));
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, &slice_hdr, ref_pic_list,
                                    ref_pic_list, ref_pic_list, ref_pic_list,
                                    pic0);
  accelerator_->SubmitSlice(&sps, &pps, &slice_hdr, ref_pic_list, ref_pic_list,
                            ref_pic_list, ref_pic_list, ref_pic_list, pic0,
                            kSliceData, sizeof(kSliceData), subsamples);

  // Save the resulting sample.
  base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample0;
  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<0>(&sample0));
  accelerator_->SubmitDecode(pic0);

  // Second frame.
  accelerator_->ProcessVPS(&vps, base::make_span(kVPS1));
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS1));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS1));
  accelerator_->SubmitFrameMetadata(&sps, &pps, &slice_hdr, ref_pic_list,
                                    ref_pic_list, ref_pic_list, ref_pic_list,
                                    pic1);
  accelerator_->SubmitSlice(&sps, &pps, &slice_hdr, ref_pic_list, ref_pic_list,
                            ref_pic_list, ref_pic_list, ref_pic_list, pic1,
                            kSliceData, sizeof(kSliceData), subsamples);

  // Save the resulting sample.
  base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample1;
  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<0>(&sample1));
  accelerator_->SubmitDecode(pic1);

  // The two samples should still have the same configurations.
  EXPECT_EQ(CMSampleBufferGetFormatDescription(sample0.get()),
            CMSampleBufferGetFormatDescription(sample1.get()));
}

}  // namespace media