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

#include <memory>

#include "base/containers/span.h"
#include "media/base/media_util.h"
#include "media/gpu/codec_picture.h"
#include "media/parsers/h264_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 buck180p30.mp4
constexpr uint8_t kSPS0[] = {0x67, 0x64, 0x00, 0x28, 0xac, 0xd1, 0x00,
                             0x78, 0x02, 0x27, 0xe5, 0xc0, 0x44, 0x00,
                             0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03,
                             0x00, 0xf0, 0x3c, 0x60, 0xc4, 0x48};
constexpr uint8_t kPPS0[] = {0x68, 0xeb, 0xef, 0x2c};

// Configuration from bbb-320x240-2video-2audio.mp4
constexpr uint8_t kSPS1[] = {0x67, 0x64, 0x00, 0x0d, 0xac, 0xd9, 0x41,
                             0x41, 0xfb, 0x0e, 0x10, 0x00, 0x00, 0x03,
                             0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0xc0,
                             0xf1, 0x42, 0x99, 0x60};
constexpr uint8_t kPPS1[] = {0x68, 0xeb, 0xe0, 0xa4, 0xb2, 0x2c};

constexpr uint8_t kSliceData[] = {0x02};

}  // namespace

class VideoToolboxH264AcceleratorTest : public testing::Test {
 public:
  VideoToolboxH264AcceleratorTest() = default;
  ~VideoToolboxH264AcceleratorTest() 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<VideoToolboxH264Accelerator> accelerator_{
      std::make_unique<VideoToolboxH264Accelerator>(
          std::make_unique<NullMediaLog>(),
          base::BindRepeating(&VideoToolboxH264AcceleratorTest::OnDecode,
                              base::Unretained(this)),
          base::BindRepeating(&VideoToolboxH264AcceleratorTest::OnOutput,
                              base::Unretained(this)))};
};

TEST_F(VideoToolboxH264AcceleratorTest, Construct) {}

TEST_F(VideoToolboxH264AcceleratorTest, DecodeOne) {
  scoped_refptr<H264Picture> pic = accelerator_->CreateH264Picture();
  H264SPS sps;
  H264PPS pps;
  H264DPB dpb;
  H264Picture::Vector ref_pic_list;
  H264SliceHeader slice_hdr;
  std::vector<SubsampleEntry> subsamples;

  // Decode frame.
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, dpb, ref_pic_list, ref_pic_list,
                                    ref_pic_list, pic);
  accelerator_->SubmitSlice(&pps, &slice_hdr, 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(VideoToolboxH264AcceleratorTest, DecodeTwo) {
  scoped_refptr<H264Picture> pic0 = accelerator_->CreateH264Picture();
  scoped_refptr<H264Picture> pic1 = accelerator_->CreateH264Picture();
  H264SPS sps;
  H264PPS pps;
  H264DPB dpb;
  H264Picture::Vector ref_pic_list;
  H264SliceHeader slice_hdr;
  std::vector<SubsampleEntry> subsamples;

  // First frame.
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, dpb, ref_pic_list, ref_pic_list,
                                    ref_pic_list, pic0);
  accelerator_->SubmitSlice(&pps, &slice_hdr, 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_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, dpb, ref_pic_list, ref_pic_list,
                                    ref_pic_list, pic1);
  accelerator_->SubmitSlice(&pps, &slice_hdr, 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(VideoToolboxH264AcceleratorTest, DecodeTwo_Reset) {
  scoped_refptr<H264Picture> pic0 = accelerator_->CreateH264Picture();
  scoped_refptr<H264Picture> pic1 = accelerator_->CreateH264Picture();
  H264SPS sps;
  H264PPS pps;
  H264DPB dpb;
  H264Picture::Vector ref_pic_list;
  H264SliceHeader slice_hdr;
  std::vector<SubsampleEntry> subsamples;

  // First frame.
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, dpb, ref_pic_list, ref_pic_list,
                                    ref_pic_list, pic0);
  accelerator_->SubmitSlice(&pps, &slice_hdr, 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_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, dpb, ref_pic_list, ref_pic_list,
                                    ref_pic_list, pic1);
  accelerator_->SubmitSlice(&pps, &slice_hdr, 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 different configurations.
  EXPECT_NE(CMSampleBufferGetFormatDescription(sample0.get()),
            CMSampleBufferGetFormatDescription(sample1.get()));
}

TEST_F(VideoToolboxH264AcceleratorTest, DecodeTwo_ConfigChange) {
  scoped_refptr<H264Picture> pic0 = accelerator_->CreateH264Picture();
  scoped_refptr<H264Picture> pic1 = accelerator_->CreateH264Picture();
  H264SPS sps;
  H264PPS pps;
  H264DPB dpb;
  H264Picture::Vector ref_pic_list;
  H264SliceHeader slice_hdr;
  std::vector<SubsampleEntry> subsamples;

  // First frame.
  accelerator_->ProcessSPS(&sps, base::make_span(kSPS0));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS0));
  accelerator_->SubmitFrameMetadata(&sps, &pps, dpb, ref_pic_list, ref_pic_list,
                                    ref_pic_list, pic0);
  accelerator_->SubmitSlice(&pps, &slice_hdr, 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_->ProcessSPS(&sps, base::make_span(kSPS1));
  accelerator_->ProcessPPS(&pps, base::make_span(kPPS1));
  accelerator_->SubmitFrameMetadata(&sps, &pps, dpb, ref_pic_list, ref_pic_list,
                                    ref_pic_list, pic1);
  accelerator_->SubmitSlice(&pps, &slice_hdr, 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 different configurations.
  EXPECT_NE(CMSampleBufferGetFormatDescription(sample0.get()),
            CMSampleBufferGetFormatDescription(sample1.get()));
}

}  // namespace media