| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/capture/video/mac/pixel_buffer_transferer_mac.h" |
| |
| #include <cmath> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "build/build_config.h" |
| #include "media/capture/video/mac/pixel_buffer_pool_mac.h" |
| #include "media/capture/video/mac/test/pixel_buffer_test_utils_mac.h" |
| #include "media/capture/video/mac/video_capture_device_avfoundation_utils_mac.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| constexpr uint8_t kColorR = 255u; |
| constexpr uint8_t kColorG = 127u; |
| constexpr uint8_t kColorB = 63u; |
| |
| // Common pixel formats that we want to test. This is partially based on |
| // VideoCaptureDeviceAVFoundation::FourCCToChromiumPixelFormat but we do not |
| // include MJPEG because compressed formats are not supported by the |
| // PixelBufferPool. In addition to the formats supported for capturing, we also |
| // test I420, which all captured formats are normally converted to in software |
| // making it a sensible destination format. |
| |
| // media::PIXEL_FORMAT_NV12 a.k.a. "420v" |
| constexpr OSType kPixelFormatNv12 = |
| kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; |
| // media::PIXEL_FORMAT_UYVY a.k.a. "2vuy" |
| constexpr OSType kPixelFormatUyvy = kCVPixelFormatType_422YpCbCr8; |
| // media::PIXEL_FORMAT_YUY2 a.k.a. "yuvs" |
| constexpr OSType kPixelFormatYuvs = kCVPixelFormatType_422YpCbCr8_yuvs; |
| // media::PIXEL_FORMAT_I420 a.k.a. "y420" |
| constexpr OSType kPixelFormatI420 = kCVPixelFormatType_420YpCbCr8Planar; |
| |
| } // namespace |
| |
| TEST(PixelBufferTransfererTest, CanCopyYuvsAndVerifyColor) { |
| constexpr OSType kPixelFormat = kPixelFormatYuvs; |
| constexpr int kWidth = 32; |
| constexpr int kHeight = 32; |
| PixelBufferTransferer transferer; |
| // Source: A single colored buffer. |
| std::unique_ptr<ByteArrayPixelBuffer> source = |
| CreateYuvsPixelBufferFromSingleRgbColor(kWidth, kHeight, kColorR, kColorG, |
| kColorB); |
| // Destination buffer: A same-sized YUVS buffer. |
| base::ScopedCFTypeRef<CVPixelBufferRef> destination = |
| PixelBufferPool::Create(kPixelFormat, kWidth, kHeight, 1)->CreateBuffer(); |
| EXPECT_TRUE(transferer.TransferImage(source->pixel_buffer, destination)); |
| // Verify the result is the same color. |
| EXPECT_TRUE(YuvsIOSurfaceIsSingleColor(CVPixelBufferGetIOSurface(destination), |
| kColorR, kColorG, kColorB)); |
| } |
| |
| #if defined(ARCH_CPU_ARM64) |
| // Bulk-disabled as part of arm64 bot stabilization: https://crbug.com/1154345 |
| #define MAYBE_CanScaleYuvsAndVerifyColor DISABLED_CanScaleYuvsAndVerifyColor |
| #else |
| #define MAYBE_CanScaleYuvsAndVerifyColor CanScaleYuvsAndVerifyColor |
| #endif |
| |
| TEST(PixelBufferTransfererTest, MAYBE_CanScaleYuvsAndVerifyColor) { |
| constexpr OSType kPixelFormat = kPixelFormatYuvs; |
| constexpr int kSourceWidth = 32; |
| constexpr int kSourceHeight = 32; |
| constexpr int kDestinationWidth = 16; |
| constexpr int kDestinationHeight = 16; |
| PixelBufferTransferer transferer; |
| // Source: A single colored buffer. |
| std::unique_ptr<ByteArrayPixelBuffer> source = |
| CreateYuvsPixelBufferFromSingleRgbColor(kSourceWidth, kSourceHeight, |
| kColorR, kColorG, kColorB); |
| // Destination buffer: A downscaled YUVS buffer. |
| base::ScopedCFTypeRef<CVPixelBufferRef> destination = |
| PixelBufferPool::Create(kPixelFormat, kDestinationWidth, |
| kDestinationHeight, 1) |
| ->CreateBuffer(); |
| EXPECT_TRUE(transferer.TransferImage(source->pixel_buffer, destination)); |
| // Verify the result is the same color. |
| EXPECT_TRUE(YuvsIOSurfaceIsSingleColor(CVPixelBufferGetIOSurface(destination), |
| kColorR, kColorG, kColorB)); |
| } |
| |
| TEST(PixelBufferTransfererTest, CanScaleYuvsAndVerifyCheckerPattern) { |
| // Note: The ARGB -> YUVS -> ARGB conversions results in a small loss of |
| // information, so for the checker pattern to be intact the buffer can't be |
| // tiny (e.g. 4x4). |
| constexpr int kSourceWidth = 64; |
| constexpr int kSourceHeight = 64; |
| constexpr int kSourceNumTilesAcross = 4; |
| constexpr int kDestinationWidth = 32; |
| constexpr int kDestinationHeight = 32; |
| PixelBufferTransferer transferer; |
| // Source: A single colored buffer. |
| std::unique_ptr<ByteArrayPixelBuffer> source = |
| CreateYuvsPixelBufferFromArgbBuffer( |
| kSourceWidth, kSourceHeight, |
| CreateArgbCheckerPatternBuffer(kSourceWidth, kSourceHeight, |
| kSourceNumTilesAcross)); |
| // Destination buffer: A downscaled YUVS buffer. |
| base::ScopedCFTypeRef<CVPixelBufferRef> destination = |
| PixelBufferPool::Create(kPixelFormatYuvs, kDestinationWidth, |
| kDestinationHeight, 1) |
| ->CreateBuffer(); |
| EXPECT_TRUE(transferer.TransferImage(source->pixel_buffer, destination)); |
| // Verify the result has the same number of checker tiles. |
| int num_tiles_across_x; |
| int num_tiles_across_y; |
| std::tie(num_tiles_across_x, num_tiles_across_y) = |
| GetCheckerPatternNumTilesAccross( |
| CreateArgbBufferFromYuvsIOSurface( |
| CVPixelBufferGetIOSurface(destination)), |
| kDestinationWidth, kDestinationHeight); |
| EXPECT_EQ(num_tiles_across_x, kSourceNumTilesAcross); |
| EXPECT_EQ(num_tiles_across_y, kSourceNumTilesAcross); |
| } |
| |
| #if defined(ARCH_CPU_ARM64) |
| // Bulk-disabled as part of arm64 bot stabilization: https://crbug.com/1154345 |
| #define MAYBE_CanStretchYuvsAndVerifyCheckerPattern \ |
| DISABLED_CanStretchYuvsAndVerifyCheckerPattern |
| #else |
| #define MAYBE_CanStretchYuvsAndVerifyCheckerPattern \ |
| CanStretchYuvsAndVerifyCheckerPattern |
| #endif |
| |
| TEST(PixelBufferTransfererTest, MAYBE_CanStretchYuvsAndVerifyCheckerPattern) { |
| // Note: The ARGB -> YUVS -> ARGB conversions results in a small loss of |
| // information, so for the checker pattern to be intact the buffer can't be |
| // tiny (e.g. 4x4). |
| constexpr int kSourceWidth = 64; |
| constexpr int kSourceHeight = 64; |
| constexpr int kSourceNumTilesAcross = 4; |
| constexpr int kDestinationWidth = 48; |
| constexpr int kDestinationHeight = 32; |
| PixelBufferTransferer transferer; |
| // Source: A single colored buffer. |
| std::unique_ptr<ByteArrayPixelBuffer> source = |
| CreateYuvsPixelBufferFromArgbBuffer( |
| kSourceWidth, kSourceHeight, |
| CreateArgbCheckerPatternBuffer(kSourceWidth, kSourceHeight, |
| kSourceNumTilesAcross)); |
| // Destination buffer: A downscaled YUVS buffer. |
| base::ScopedCFTypeRef<CVPixelBufferRef> destination = |
| PixelBufferPool::Create(kPixelFormatYuvs, kDestinationWidth, |
| kDestinationHeight, 1) |
| ->CreateBuffer(); |
| EXPECT_TRUE(transferer.TransferImage(source->pixel_buffer, destination)); |
| // Verify the result has the same number of checker tiles. |
| int num_tiles_across_x; |
| int num_tiles_across_y; |
| std::tie(num_tiles_across_x, num_tiles_across_y) = |
| GetCheckerPatternNumTilesAccross( |
| CreateArgbBufferFromYuvsIOSurface( |
| CVPixelBufferGetIOSurface(destination)), |
| kDestinationWidth, kDestinationHeight); |
| EXPECT_EQ(num_tiles_across_x, kSourceNumTilesAcross); |
| EXPECT_EQ(num_tiles_across_y, kSourceNumTilesAcross); |
| } |
| |
| #if defined(ARCH_CPU_ARM64) |
| // Bulk-disabled as part of arm64 bot stabilization: https://crbug.com/1154345 |
| #define MAYBE_CanStretchYuvsAndVerifyColor DISABLED_CanStretchYuvsAndVerifyColor |
| #else |
| #define MAYBE_CanStretchYuvsAndVerifyColor CanStretchYuvsAndVerifyColor |
| #endif |
| |
| TEST(PixelBufferTransfererTest, MAYBE_CanStretchYuvsAndVerifyColor) { |
| constexpr OSType kPixelFormat = kPixelFormatYuvs; |
| constexpr int kSourceWidth = 32; |
| constexpr int kSourceHeight = 32; |
| constexpr int kDestinationWidth = 48; // Aspect ratio does not match source. |
| constexpr int kDestinationHeight = 16; |
| PixelBufferTransferer transferer; |
| // Source: A single colored buffer. |
| std::unique_ptr<ByteArrayPixelBuffer> source = |
| CreateYuvsPixelBufferFromSingleRgbColor(kSourceWidth, kSourceHeight, |
| kColorR, kColorG, kColorB); |
| // Destination buffer: A streched YUVS buffer. |
| base::ScopedCFTypeRef<CVPixelBufferRef> destination = |
| PixelBufferPool::Create(kPixelFormat, kDestinationWidth, |
| kDestinationHeight, 1) |
| ->CreateBuffer(); |
| EXPECT_TRUE(transferer.TransferImage(source->pixel_buffer, destination)); |
| // Verify the result is the same color. |
| EXPECT_TRUE(YuvsIOSurfaceIsSingleColor(CVPixelBufferGetIOSurface(destination), |
| kColorR, kColorG, kColorB)); |
| } |
| |
| TEST(PixelBufferTransfererTest, CanConvertAndStretchSimultaneouslyYuvsToNv12) { |
| // Source pixel format: YUVS |
| constexpr int kSourceWidth = 32; |
| constexpr int kSourceHeight = 32; |
| constexpr OSType kDestinationPixelFormat = kPixelFormatNv12; |
| constexpr int kDestinationWidth = 48; // Aspect ratio does not match source. |
| constexpr int kDestinationHeight = 16; |
| PixelBufferTransferer transferer; |
| // Source: A single colored buffer. |
| std::unique_ptr<ByteArrayPixelBuffer> source = |
| CreateYuvsPixelBufferFromSingleRgbColor(kSourceWidth, kSourceHeight, |
| kColorR, kColorG, kColorB); |
| // Destination buffer: A streched NV12 buffer. |
| base::ScopedCFTypeRef<CVPixelBufferRef> destination = |
| PixelBufferPool::Create(kDestinationPixelFormat, kDestinationWidth, |
| kDestinationHeight, 1) |
| ->CreateBuffer(); |
| EXPECT_TRUE(transferer.TransferImage(source->pixel_buffer, destination)); |
| } |
| |
| class PixelBufferTransfererParameterizedTest |
| : public ::testing::Test, |
| public ::testing::WithParamInterface<std::tuple<OSType, OSType>> {}; |
| |
| // We do not have the testing utils necessary to create and verify pixel buffers |
| // in other formats than YUVS, so in order to test the full conversion matrix of |
| // all supported formats X -> Y, this parameterized test performs: |
| // YUVS -> X -> Y -> YUVS |
| TEST_P(PixelBufferTransfererParameterizedTest, |
| CanConvertFromXToYAndVerifyColor) { |
| OSType pixel_format_from; |
| OSType pixel_format_to; |
| std::tie(pixel_format_from, pixel_format_to) = GetParam(); |
| LOG(INFO) << "Running Test: " << MacFourCCToString(pixel_format_from) |
| << " -> " << MacFourCCToString(pixel_format_to); |
| |
| constexpr int kWidth = 32; |
| constexpr int kHeight = 32; |
| PixelBufferTransferer transferer; |
| // We always start with YUVS because this is the format that the testing |
| // utilities can convert to/from RGB. |
| std::unique_ptr<ByteArrayPixelBuffer> original_yuvs_buffer = |
| CreateYuvsPixelBufferFromSingleRgbColor(kWidth, kHeight, kColorR, kColorG, |
| kColorB); |
| // YUVS -> pixel_format_from |
| base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer_from; |
| if (pixel_format_from == kPixelFormatYuvs) { |
| pixel_buffer_from = original_yuvs_buffer->pixel_buffer; |
| } else { |
| pixel_buffer_from = |
| PixelBufferPool::Create(pixel_format_from, kWidth, kHeight, 1) |
| ->CreateBuffer(); |
| transferer.TransferImage(original_yuvs_buffer->pixel_buffer, |
| pixel_buffer_from); |
| } |
| ASSERT_TRUE(pixel_buffer_from); |
| |
| // pixel_format_from -> pixel_format_to |
| base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer_to = |
| PixelBufferPool::Create(pixel_format_to, kWidth, kHeight, 1) |
| ->CreateBuffer(); |
| EXPECT_TRUE(transferer.TransferImage(pixel_buffer_from, pixel_buffer_to)); |
| |
| // We always convert back to YUVS because this is the only format that the |
| // testing utilities can convert to/from RGB. |
| base::ScopedCFTypeRef<CVPixelBufferRef> final_yuvs_buffer; |
| // pixel_format_to -> YUVS |
| if (pixel_format_to == kPixelFormatYuvs) { |
| final_yuvs_buffer = pixel_buffer_to; |
| } else { |
| final_yuvs_buffer = |
| PixelBufferPool::Create(kPixelFormatYuvs, kWidth, kHeight, 1) |
| ->CreateBuffer(); |
| transferer.TransferImage(pixel_buffer_to, final_yuvs_buffer); |
| } |
| ASSERT_TRUE(final_yuvs_buffer); |
| // Verify that after our "conversion dance" we end up with the same color that |
| // we started with. |
| EXPECT_TRUE(YuvsIOSurfaceIsSingleColor( |
| CVPixelBufferGetIOSurface(final_yuvs_buffer), kColorR, kColorG, kColorB)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PixelBufferTransfererTest, |
| PixelBufferTransfererParameterizedTest, |
| ::testing::Combine(::testing::Values(kPixelFormatNv12, |
| kPixelFormatUyvy, |
| kPixelFormatYuvs, |
| kPixelFormatI420), |
| ::testing::Values(kPixelFormatNv12, |
| kPixelFormatUyvy, |
| kPixelFormatYuvs, |
| kPixelFormatI420))); |
| |
| } // namespace media |