chromium/chrome/services/media_gallery_util/media_parser_android_unittest.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/services/media_gallery_util/media_parser_android.h"

#include <memory>
#include <vector>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "chrome/services/media_gallery_util/public/mojom/media_parser.mojom.h"
#include "media/base/supported_types.h"
#include "media/base/test_data_util.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

#if BUILDFLAG(USE_PROPRIETARY_CODECS)
// Returns if the first 3 or 4 bytes of H26x encoded |data| is the start code,
// 0x000001 or 0x00000001.
bool HasH26xStartCode(const std::vector<uint8_t>& data) {
  if (data.size() < 4 || (data[0] != 0u || data[1] != 0u))
    return false;
  return data[2] == 0x01 || (data[2] == 0u && data[3] == 0x01);
}
#endif

// Returns if the first few bytes in the YUV frame are not all zero. This is a
// rough method to verify the frame is not empty.
bool HasValidYUVData(const media::VideoFrame& frame) {
  bool valid = false;
  for (size_t i = 0; i < 8; ++i) {
    valid |= *(frame.data(media::VideoFrame::Plane::kY) + i);
    if (valid)
      break;
  }
  return valid;
}

// Used in test that do blocking reads from a local file.
class TestMediaDataSource : public chrome::mojom::MediaDataSource {
 public:
  TestMediaDataSource(
      mojo::PendingReceiver<chrome::mojom::MediaDataSource> receiver,
      const base::FilePath& file_path)
      : file_path_(file_path), receiver_(this, std::move(receiver)) {}

  TestMediaDataSource(const TestMediaDataSource&) = delete;
  TestMediaDataSource& operator=(const TestMediaDataSource&) = delete;

  ~TestMediaDataSource() override = default;

 private:
  // chrome::mojom::MediaDataSource implementation.
  void Read(int64_t position,
            int64_t length,
            chrome::mojom::MediaDataSource::ReadCallback callback) override {
    base::File file(file_path_, base::File::Flags::FLAG_OPEN |
                                    base::File::Flags::FLAG_READ);
    auto buffer = std::vector<uint8_t>(length);
    int bytes_read = file.Read(position, (char*)(buffer.data()), length);
    if (bytes_read < length)
      buffer.resize(bytes_read);

    std::move(callback).Run(std::vector<uint8_t>(std::move(buffer)));
  }

  base::FilePath file_path_;
  mojo::Receiver<chrome::mojom::MediaDataSource> receiver_;
};

class MediaParserAndroidTest : public testing::Test {
 public:
  MediaParserAndroidTest() = default;

  MediaParserAndroidTest(const MediaParserAndroidTest&) = delete;
  MediaParserAndroidTest& operator=(const MediaParserAndroidTest&) = delete;

  ~MediaParserAndroidTest() override = default;

  void SetUp() override {
    parser_ = std::make_unique<MediaParserAndroid>();
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
  }

  void TearDown() override { parser_.reset(); }

 protected:
  MediaParserAndroid* parser() { return parser_.get(); }

  const base::FilePath& temp_dir() const { return temp_dir_.GetPath(); }

  chrome::mojom::ExtractVideoFrameResultPtr ExtractFrame(
      const base::FilePath& file_path,
      const std::string& mime_type) {
    int64_t size = 0;
    EXPECT_TRUE(base::GetFileSize(file_path, &size));

    mojo::PendingRemote<chrome::mojom::MediaDataSource> remote_data_source;
    TestMediaDataSource test_data_source(
        remote_data_source.InitWithNewPipeAndPassReceiver(), file_path);

    chrome::mojom::ExtractVideoFrameResultPtr result;
    base::RunLoop run_loop;
    parser()->ExtractVideoFrame(
        mime_type, size, std::move(remote_data_source),
        base::BindLambdaForTesting(
            [&](chrome::mojom::ExtractVideoFrameResultPtr mojo_result) {
              result = std::move(mojo_result);
              run_loop.Quit();
            }));
    run_loop.Run();

    return result;
  }

 private:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<MediaParserAndroid> parser_;
  base::ScopedTempDir temp_dir_;
};

#if BUILDFLAG(USE_PROPRIETARY_CODECS)
// Test to verify an encoded video frame can be extracted for h264 codec video
// file. Decoding needs to happen in other process.
TEST_F(MediaParserAndroidTest, VideoFrameExtractionH264) {
  chrome::mojom::ExtractVideoFrameResultPtr result =
      ExtractFrame(media::GetTestDataFilePath("bear.mp4"), "video/mp4");
  ASSERT_TRUE(result);

  if (media::IsBuiltInVideoCodec(media::VideoCodec::kH264)) {
    const auto& frame = result->frame_data->get_decoded_frame();
    ASSERT_TRUE(frame);
    EXPECT_TRUE(HasValidYUVData(*frame));
    EXPECT_TRUE(frame->IsMappable());
    EXPECT_FALSE(frame->HasTextures());
    EXPECT_EQ(frame->storage_type(),
              media::VideoFrame::StorageType::STORAGE_OWNED_MEMORY);
  } else {
    EXPECT_EQ(result->frame_data->which(),
              chrome::mojom::VideoFrameData::Tag::kEncodedData);
    EXPECT_FALSE(result->frame_data->get_encoded_data().empty());
    EXPECT_TRUE(HasH26xStartCode(result->frame_data->get_encoded_data()));
  }
}

TEST_F(MediaParserAndroidTest, VideoFrameExtractionH265) {
  chrome::mojom::ExtractVideoFrameResultPtr result = ExtractFrame(
      media::GetTestDataFilePath("bear-hevc-frag.mp4"), "video/mp4");
  ASSERT_TRUE(result);
  EXPECT_EQ(result->frame_data->which(),
            chrome::mojom::VideoFrameData::Tag::kEncodedData);
  EXPECT_FALSE(result->frame_data->get_encoded_data().empty());
  EXPECT_TRUE(HasH26xStartCode(result->frame_data->get_encoded_data()));
}
#endif

// Test to verify a decoded video frame can be extracted for vp8 codec video
// file with YUV420 color format.
TEST_F(MediaParserAndroidTest, VideoFrameExtractionVp8) {
  chrome::mojom::ExtractVideoFrameResultPtr result = ExtractFrame(
      media::GetTestDataFilePath("bear-vp8-webvtt.webm"), "video/webm");
  ASSERT_TRUE(result);
  EXPECT_EQ(result->frame_data->which(),
            chrome::mojom::VideoFrameData::Tag::kDecodedFrame);
  const auto& frame = result->frame_data->get_decoded_frame();
  ASSERT_TRUE(frame);
  EXPECT_TRUE(HasValidYUVData(*frame));
  EXPECT_TRUE(frame->IsMappable());
  EXPECT_FALSE(frame->HasTextures());
  EXPECT_EQ(frame->storage_type(),
            media::VideoFrame::StorageType::STORAGE_OWNED_MEMORY);
}

// Test to verify a decoded video frame can be extracted for vp8 codec with
// alpha plane.
TEST_F(MediaParserAndroidTest, VideoFrameExtractionVp8WithAlphaPlane) {
  chrome::mojom::ExtractVideoFrameResultPtr result =
      ExtractFrame(media::GetTestDataFilePath("bear-vp8a.webm"), "video/webm");
  ASSERT_TRUE(result);

  EXPECT_EQ(result->frame_data->which(),
            chrome::mojom::VideoFrameData::Tag::kDecodedFrame);
  const auto& frame = result->frame_data->get_decoded_frame();
  ASSERT_TRUE(frame);
  EXPECT_TRUE(HasValidYUVData(*frame));
  EXPECT_TRUE(frame->IsMappable());
  EXPECT_FALSE(frame->HasTextures());
  EXPECT_EQ(frame->storage_type(),
            media::VideoFrame::StorageType::STORAGE_OWNED_MEMORY);
}

TEST_F(MediaParserAndroidTest, VideoFrameExtractionVp9) {
  chrome::mojom::ExtractVideoFrameResultPtr result =
      ExtractFrame(media::GetTestDataFilePath("bear-vp9.webm"), "video/webm");
  ASSERT_TRUE(result);
  EXPECT_EQ(result->frame_data->which(),
            chrome::mojom::VideoFrameData::Tag::kDecodedFrame);
  const auto& frame = result->frame_data->get_decoded_frame();
  ASSERT_TRUE(frame);
  EXPECT_TRUE(HasValidYUVData(*frame));
  EXPECT_TRUE(frame->IsMappable());
  EXPECT_FALSE(frame->HasTextures());
  EXPECT_EQ(frame->storage_type(),
            media::VideoFrame::StorageType::STORAGE_UNOWNED_MEMORY);
}

TEST_F(MediaParserAndroidTest, VideoFrameExtractionAv1) {
  chrome::mojom::ExtractVideoFrameResultPtr result =
      ExtractFrame(media::GetTestDataFilePath("bear-av1.mp4"), "video/mp4");
  ASSERT_TRUE(result);
  EXPECT_EQ(result->frame_data->which(),
            chrome::mojom::VideoFrameData::Tag::kDecodedFrame);
  const auto& frame = result->frame_data->get_decoded_frame();
  ASSERT_TRUE(frame);
  EXPECT_TRUE(HasValidYUVData(*frame));
  EXPECT_TRUE(frame->IsMappable());
  EXPECT_FALSE(frame->HasTextures());
  EXPECT_EQ(frame->storage_type(),
            media::VideoFrame::StorageType::STORAGE_UNOWNED_MEMORY);
}

// Test to verify frame extraction will fail on invalid video file.
TEST_F(MediaParserAndroidTest, VideoFrameExtractionInvalidFile) {
  base::FilePath dummy_file = temp_dir().AppendASCII("test.txt");
  EXPECT_TRUE(base::WriteFile(dummy_file, "123"));

  EXPECT_FALSE(ExtractFrame(dummy_file, "video/webm"));
}

}  // namespace