Commit 5110c3d3 authored by Henrik Boström's avatar Henrik Boström Committed by Commit Bot

[macOS Capture] Move ARGB/YUVS pixel buffer helpers into utils file.

The pixel_buffer_transferer_mac_unittest.mm defines several helper
functions for creating ARGB and YUVS buffers from a single color or
with a checker pattern, converting between the two formats, and
verifying the color or number of tiles of a pixel buffer.

Because these are useful for sanity checks involving creating or
modifying pixel buffers, we move these to a separate utilities file
(only available in testing). To be used by future CLs' unittests.

Bug: chromium:1132299
Change-Id: Idd6c3f8918b540240f515e2761695e9b5bc9caa2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2498564
Commit-Queue: Markus Handell <handellm@google.com>
Reviewed-by: default avatarMarkus Handell <handellm@google.com>
Cr-Commit-Position: refs/heads/master@{#820785}
parent f5d84ffa
...@@ -474,6 +474,8 @@ test("capture_unittests") { ...@@ -474,6 +474,8 @@ test("capture_unittests") {
"video/mac/pixel_buffer_transferer_mac_unittest.mm", "video/mac/pixel_buffer_transferer_mac_unittest.mm",
"video/mac/test/mock_video_capture_device_avfoundation_frame_receiver_mac.h", "video/mac/test/mock_video_capture_device_avfoundation_frame_receiver_mac.h",
"video/mac/test/mock_video_capture_device_avfoundation_frame_receiver_mac.mm", "video/mac/test/mock_video_capture_device_avfoundation_frame_receiver_mac.mm",
"video/mac/test/pixel_buffer_test_utils_mac.cc",
"video/mac/test/pixel_buffer_test_utils_mac.h",
"video/mac/test/video_capture_test_utils_mac.h", "video/mac/test/video_capture_test_utils_mac.h",
"video/mac/test/video_capture_test_utils_mac.mm", "video/mac/test/video_capture_test_utils_mac.mm",
"video/mac/video_capture_device_avfoundation_mac_unittest.mm", "video/mac/video_capture_device_avfoundation_mac_unittest.mm",
......
...@@ -9,11 +9,10 @@ ...@@ -9,11 +9,10 @@
#include "base/logging.h" #include "base/logging.h"
#include "media/capture/video/mac/pixel_buffer_pool_mac.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 "media/capture/video/mac/video_capture_device_avfoundation_utils_mac.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libyuv/include/libyuv/convert_argb.h"
#include "third_party/libyuv/include/libyuv/convert_from_argb.h"
namespace media { namespace media {
...@@ -40,171 +39,6 @@ constexpr OSType kPixelFormatYuvs = kCVPixelFormatType_422YpCbCr8_yuvs; ...@@ -40,171 +39,6 @@ constexpr OSType kPixelFormatYuvs = kCVPixelFormatType_422YpCbCr8_yuvs;
// media::PIXEL_FORMAT_I420 a.k.a. "y420" // media::PIXEL_FORMAT_I420 a.k.a. "y420"
constexpr OSType kPixelFormatI420 = kCVPixelFormatType_420YpCbCr8Planar; constexpr OSType kPixelFormatI420 = kCVPixelFormatType_420YpCbCr8Planar;
// ARGB has 4 bytes per pixel and no padding.
size_t GetArgbStride(size_t width) {
return width * 4;
}
// YUVS is a 4:2:2 pixel format that is packed to be 2 bytes per pixel.
// https://gstreamer.freedesktop.org/documentation/additional/design/mediatype-video-raw.html?gi-language=c
size_t GetYuvsStride(size_t width) {
return width * 2;
}
std::vector<uint8_t> CreateArgbBufferFromSingleRgbColor(int width,
int height,
uint8_t r,
uint8_t g,
uint8_t b) {
std::vector<uint8_t> argb_buffer;
argb_buffer.resize(GetArgbStride(width) * height);
for (size_t i = 0; i < argb_buffer.size(); i += 4) {
// ARGB little endian = BGRA in memory.
argb_buffer[i + 0] = b;
argb_buffer[i + 1] = g;
argb_buffer[i + 2] = r;
argb_buffer[i + 3] = 255u;
}
return argb_buffer;
}
bool IsArgbPixelWhite(const std::vector<uint8_t>& argb_buffer, size_t i) {
// ARGB little endian = BGRA in memory.
uint8_t b = argb_buffer[i + 0];
uint8_t g = argb_buffer[i + 1];
uint8_t r = argb_buffer[i + 2];
return (r + g + b) / 3 >= 255 / 2;
}
bool ArgbBufferIsSingleColor(const std::vector<uint8_t>& argb_buffer,
uint8_t r,
uint8_t g,
uint8_t b) {
int signed_r = r;
int signed_g = g;
int signed_b = b;
// ~5% error tolerance.
constexpr int kErrorTolerance = 0.05 * 255;
for (size_t i = 0; i < argb_buffer.size(); i += 4) {
// ARGB little endian = BGRA in memory.
int pixel_b = argb_buffer[i + 0];
int pixel_g = argb_buffer[i + 1];
int pixel_r = argb_buffer[i + 2];
if (std::abs(pixel_r - signed_r) > kErrorTolerance ||
std::abs(pixel_g - signed_g) > kErrorTolerance ||
std::abs(pixel_b - signed_b) > kErrorTolerance) {
return false;
}
}
return true;
}
std::vector<uint8_t> CreateArgbCheckerPatternBuffer(int width,
int height,
int num_tiles_across) {
std::vector<uint8_t> argb_buffer;
int tile_width = width / num_tiles_across;
int tile_height = height / num_tiles_across;
argb_buffer.resize(GetArgbStride(width) * height);
for (size_t i = 0; i < argb_buffer.size(); i += 4) {
size_t pixel_number = i / 4;
size_t x = pixel_number % width;
size_t y = pixel_number / width;
bool is_white = ((x / tile_width) % 2 != 0) != ((y / tile_height) % 2 != 0);
// ARGB little endian = BGRA in memory.
argb_buffer[i + 0] = argb_buffer[i + 1] = argb_buffer[i + 2] =
is_white ? 255u : 100u;
argb_buffer[i + 3] = 255u;
}
return argb_buffer;
}
std::tuple<int, int> GetCheckerPatternNumTilesAccross(
const std::vector<uint8_t>& argb_buffer,
int width,
int height) {
int num_tiles_across_x = 0;
bool prev_tile_is_white = false;
for (int x = 0; x < width; ++x) {
size_t i = (x * 4);
bool current_tile_is_white = IsArgbPixelWhite(argb_buffer, i);
if (x == 0 || prev_tile_is_white != current_tile_is_white) {
prev_tile_is_white = current_tile_is_white;
++num_tiles_across_x;
}
}
int num_tiles_across_y = 0;
prev_tile_is_white = false;
for (int y = 0; y < height; ++y) {
size_t i = y * GetArgbStride(width);
bool current_tile_is_white = IsArgbPixelWhite(argb_buffer, i);
if (y == 0 || prev_tile_is_white != current_tile_is_white) {
prev_tile_is_white = current_tile_is_white;
++num_tiles_across_y;
}
}
return std::make_pair(num_tiles_across_x, num_tiles_across_y);
}
struct ByteArrayPixelBuffer {
std::vector<uint8_t> byte_array;
base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer;
};
std::unique_ptr<ByteArrayPixelBuffer> CreateYuvsPixelBufferFromArgbBuffer(
int width,
int height,
const std::vector<uint8_t>& argb_buffer) {
std::unique_ptr<ByteArrayPixelBuffer> result =
std::make_unique<ByteArrayPixelBuffer>();
size_t yuvs_stride = GetYuvsStride(width);
// ARGB -> YUVS (a.k.a. YUY2).
result->byte_array.resize(yuvs_stride * height);
libyuv::ARGBToYUY2(&argb_buffer[0], GetArgbStride(width),
&result->byte_array[0], yuvs_stride, width, height);
CVReturn error = CVPixelBufferCreateWithBytes(
nil, width, height, kPixelFormatYuvs, (void*)&result->byte_array[0],
yuvs_stride, nil, nil, nil, result->pixel_buffer.InitializeInto());
CHECK(error == noErr);
return result;
}
std::unique_ptr<ByteArrayPixelBuffer> CreateYuvsPixelBufferFromSingleRgbColor(
int width,
int height,
uint8_t r,
uint8_t g,
uint8_t b) {
return CreateYuvsPixelBufferFromArgbBuffer(
width, height,
CreateArgbBufferFromSingleRgbColor(width, height, r, g, b));
}
std::vector<uint8_t> CreateArgbBufferFromYuvsIOSurface(
IOSurfaceRef io_surface) {
DCHECK(io_surface);
size_t width = IOSurfaceGetWidth(io_surface);
size_t height = IOSurfaceGetHeight(io_surface);
size_t argb_stride = GetArgbStride(width);
size_t yuvs_stride = GetYuvsStride(width);
uint8_t* pixels = static_cast<uint8_t*>(IOSurfaceGetBaseAddress(io_surface));
DCHECK(pixels);
std::vector<uint8_t> argb_buffer;
argb_buffer.resize(argb_stride * height);
libyuv::YUY2ToARGB(pixels, yuvs_stride, &argb_buffer[0], argb_stride, width,
height);
return argb_buffer;
}
bool YuvsIOSurfaceIsSingleColor(IOSurfaceRef io_surface,
uint8_t r,
uint8_t g,
uint8_t b) {
return ArgbBufferIsSingleColor(CreateArgbBufferFromYuvsIOSurface(io_surface),
r, g, b);
}
} // namespace } // namespace
TEST(PixelBufferTransfererTest, CanCopyYuvsAndVerifyColor) { TEST(PixelBufferTransfererTest, CanCopyYuvsAndVerifyColor) {
......
// 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/test/pixel_buffer_test_utils_mac.h"
#include "third_party/libyuv/include/libyuv/convert_argb.h"
#include "third_party/libyuv/include/libyuv/convert_from_argb.h"
namespace media {
namespace {
// media::PIXEL_FORMAT_YUY2 a.k.a. "yuvs"
constexpr OSType kPixelFormatYuvs = kCVPixelFormatType_422YpCbCr8_yuvs;
// ARGB has 4 bytes per pixel and no padding.
size_t GetArgbStride(size_t width) {
return width * 4;
}
// YUVS is a 4:2:2 pixel format that is packed to be 2 bytes per pixel.
// https://gstreamer.freedesktop.org/documentation/additional/design/mediatype-video-raw.html?gi-language=c
size_t GetYuvsStride(size_t width) {
return width * 2;
}
} // namespace
ByteArrayPixelBuffer::ByteArrayPixelBuffer() {}
ByteArrayPixelBuffer::~ByteArrayPixelBuffer() {}
std::vector<uint8_t> CreateArgbBufferFromSingleRgbColor(int width,
int height,
uint8_t r,
uint8_t g,
uint8_t b) {
std::vector<uint8_t> argb_buffer;
argb_buffer.resize(GetArgbStride(width) * height);
for (size_t i = 0; i < argb_buffer.size(); i += 4) {
// ARGB little endian = BGRA in memory.
argb_buffer[i + 0] = b;
argb_buffer[i + 1] = g;
argb_buffer[i + 2] = r;
argb_buffer[i + 3] = 255u;
}
return argb_buffer;
}
bool IsArgbPixelWhite(const std::vector<uint8_t>& argb_buffer, size_t i) {
// ARGB little endian = BGRA in memory.
uint8_t b = argb_buffer[i + 0];
uint8_t g = argb_buffer[i + 1];
uint8_t r = argb_buffer[i + 2];
return (r + g + b) / 3 >= 255 / 2;
}
bool ArgbBufferIsSingleColor(const std::vector<uint8_t>& argb_buffer,
uint8_t r,
uint8_t g,
uint8_t b) {
int signed_r = r;
int signed_g = g;
int signed_b = b;
// ~5% error tolerance.
constexpr int kErrorTolerance = 0.05 * 255;
for (size_t i = 0; i < argb_buffer.size(); i += 4) {
// ARGB little endian = BGRA in memory.
int pixel_b = argb_buffer[i + 0];
int pixel_g = argb_buffer[i + 1];
int pixel_r = argb_buffer[i + 2];
if (std::abs(pixel_r - signed_r) > kErrorTolerance ||
std::abs(pixel_g - signed_g) > kErrorTolerance ||
std::abs(pixel_b - signed_b) > kErrorTolerance) {
return false;
}
}
return true;
}
std::vector<uint8_t> CreateArgbCheckerPatternBuffer(int width,
int height,
int num_tiles_across) {
std::vector<uint8_t> argb_buffer;
int tile_width = width / num_tiles_across;
int tile_height = height / num_tiles_across;
argb_buffer.resize(GetArgbStride(width) * height);
for (size_t i = 0; i < argb_buffer.size(); i += 4) {
size_t pixel_number = i / 4;
size_t x = pixel_number % width;
size_t y = pixel_number / width;
bool is_white = ((x / tile_width) % 2 != 0) != ((y / tile_height) % 2 != 0);
// ARGB little endian = BGRA in memory.
argb_buffer[i + 0] = argb_buffer[i + 1] = argb_buffer[i + 2] =
is_white ? 255u : 100u;
argb_buffer[i + 3] = 255u;
}
return argb_buffer;
}
std::tuple<int, int> GetCheckerPatternNumTilesAccross(
const std::vector<uint8_t>& argb_buffer,
int width,
int height) {
int num_tiles_across_x = 0;
bool prev_tile_is_white = false;
for (int x = 0; x < width; ++x) {
size_t i = (x * 4);
bool current_tile_is_white = IsArgbPixelWhite(argb_buffer, i);
if (x == 0 || prev_tile_is_white != current_tile_is_white) {
prev_tile_is_white = current_tile_is_white;
++num_tiles_across_x;
}
}
int num_tiles_across_y = 0;
prev_tile_is_white = false;
for (int y = 0; y < height; ++y) {
size_t i = y * GetArgbStride(width);
bool current_tile_is_white = IsArgbPixelWhite(argb_buffer, i);
if (y == 0 || prev_tile_is_white != current_tile_is_white) {
prev_tile_is_white = current_tile_is_white;
++num_tiles_across_y;
}
}
return std::make_pair(num_tiles_across_x, num_tiles_across_y);
}
std::unique_ptr<ByteArrayPixelBuffer> CreateYuvsPixelBufferFromArgbBuffer(
int width,
int height,
const std::vector<uint8_t>& argb_buffer) {
std::unique_ptr<ByteArrayPixelBuffer> result =
std::make_unique<ByteArrayPixelBuffer>();
size_t yuvs_stride = GetYuvsStride(width);
// ARGB -> YUVS (a.k.a. YUY2).
result->byte_array.resize(yuvs_stride * height);
libyuv::ARGBToYUY2(&argb_buffer[0], GetArgbStride(width),
&result->byte_array[0], yuvs_stride, width, height);
CVReturn error = CVPixelBufferCreateWithBytes(
nil, width, height, kPixelFormatYuvs, (void*)&result->byte_array[0],
yuvs_stride, nil, nil, nil, result->pixel_buffer.InitializeInto());
CHECK(error == noErr);
return result;
}
std::unique_ptr<ByteArrayPixelBuffer> CreateYuvsPixelBufferFromSingleRgbColor(
int width,
int height,
uint8_t r,
uint8_t g,
uint8_t b) {
return CreateYuvsPixelBufferFromArgbBuffer(
width, height,
CreateArgbBufferFromSingleRgbColor(width, height, r, g, b));
}
std::vector<uint8_t> CreateArgbBufferFromYuvsIOSurface(
IOSurfaceRef io_surface) {
DCHECK(io_surface);
size_t width = IOSurfaceGetWidth(io_surface);
size_t height = IOSurfaceGetHeight(io_surface);
size_t argb_stride = GetArgbStride(width);
size_t yuvs_stride = GetYuvsStride(width);
uint8_t* pixels = static_cast<uint8_t*>(IOSurfaceGetBaseAddress(io_surface));
DCHECK(pixels);
std::vector<uint8_t> argb_buffer;
argb_buffer.resize(argb_stride * height);
libyuv::YUY2ToARGB(pixels, yuvs_stride, &argb_buffer[0], argb_stride, width,
height);
return argb_buffer;
}
bool YuvsIOSurfaceIsSingleColor(IOSurfaceRef io_surface,
uint8_t r,
uint8_t g,
uint8_t b) {
return ArgbBufferIsSingleColor(CreateArgbBufferFromYuvsIOSurface(io_surface),
r, g, b);
}
} // namespace media
// 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.
#ifndef MEDIA_CAPTURE_VIDEO_MAC_TEST_PIXEL_BUFFER_TEST_UTILS_MAC_H_
#define MEDIA_CAPTURE_VIDEO_MAC_TEST_PIXEL_BUFFER_TEST_UTILS_MAC_H_
#include <memory>
#include <tuple>
#include <vector>
#import <CoreVideo/CoreVideo.h>
#import <IOSurface/IOSurface.h>
#include "base/mac/scoped_cftyperef.h"
namespace media {
struct ByteArrayPixelBuffer {
ByteArrayPixelBuffer();
~ByteArrayPixelBuffer();
std::vector<uint8_t> byte_array;
base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer;
};
// All pixels of the resulting buffer have the specified RGB. Alpha is 255.
std::vector<uint8_t> CreateArgbBufferFromSingleRgbColor(int width,
int height,
uint8_t r,
uint8_t g,
uint8_t b);
// True if the RGB color is closer to white (255,255,255) than black (0,0,0).
bool IsArgbPixelWhite(const std::vector<uint8_t>& argb_buffer, size_t i);
// True if all pixels are the specified RGB color, within some margin of error.
bool ArgbBufferIsSingleColor(const std::vector<uint8_t>& argb_buffer,
uint8_t r,
uint8_t g,
uint8_t b);
// Creates a checker pattern with tiles being white (255,255,255) or dark gray
// (100,100,100). Alpha is 255. The top-left corner is white.
std::vector<uint8_t> CreateArgbCheckerPatternBuffer(int width,
int height,
int num_tiles_across);
// Traverse the top and left border pixels, counting the number of tiles if this
// is a checker pattern. (Each pixel is characterized as white or non-white and
// this function counts the number of transitions between these colors.)
std::tuple<int, int> GetCheckerPatternNumTilesAccross(
const std::vector<uint8_t>& argb_buffer,
int width,
int height);
// Creates a CVPixelBuffer that is backed by an in-memory byte array.
std::unique_ptr<ByteArrayPixelBuffer> CreateYuvsPixelBufferFromArgbBuffer(
int width,
int height,
const std::vector<uint8_t>& argb_buffer);
std::unique_ptr<ByteArrayPixelBuffer> CreateYuvsPixelBufferFromSingleRgbColor(
int width,
int height,
uint8_t r,
uint8_t g,
uint8_t b);
// YUVS IOSurface -> ARGB byte array.
std::vector<uint8_t> CreateArgbBufferFromYuvsIOSurface(IOSurfaceRef io_surface);
// True if all pixels of the YUVS IOSurface are the specified RGB color, within
// some margin of error.
bool YuvsIOSurfaceIsSingleColor(IOSurfaceRef io_surface,
uint8_t r,
uint8_t g,
uint8_t b);
} // namespace media
#endif // MEDIA_CAPTURE_VIDEO_MAC_TEST_PIXEL_BUFFER_TEST_UTILS_MAC_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment