blob: bf561594ce6bd8f49f361d04351d56fa71ee4f94 [file]
/*
* Copyright 2017 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "common/camera_buffer_manager_impl.h"
#include <drm_fourcc.h>
#include <gbm.h>
#include <sys/mman.h>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include <base/at_exit.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "common/camera_buffer_handle.h"
#include "hardware_buffer/allocator.h"
namespace cros::tests {
using ::testing::A;
using ::testing::ByMove;
using ::testing::Return;
void* fake_addr = reinterpret_cast<void*>(0xbeefdead);
class MockAllocator : public Allocator {
public:
MOCK_METHOD(void, DestroyAllocator, ());
~MockAllocator() override { DestroyAllocator(); }
MOCK_METHOD(std::unique_ptr<Allocator::BufferObject>,
CreateBo,
(int width, int height, uint32_t drm_format, uint32_t gbm_flags),
(override));
MOCK_METHOD(std::unique_ptr<Allocator::BufferObject>,
ImportBo,
(const ImportData& data),
(override));
MOCK_METHOD(bool,
IsFormatSupported,
(uint32_t drm_format, uint32_t gbm_flags),
(override));
};
class MockBufferObject : public Allocator::BufferObject {
public:
MOCK_METHOD(void, DestroyBo, ());
~MockBufferObject() override { DestroyBo(); }
MOCK_METHOD(BufferDescriptor, Describe, (), (const, override));
MOCK_METHOD(bool,
BeginCpuAccess,
(SyncType sync_type, int plane),
(override));
MOCK_METHOD(bool, EndCpuAccess, (SyncType sync_type, int plane), (override));
MOCK_METHOD(bool, Map, (int plane), (override));
MOCK_METHOD(void, Unmap, (int plane), (override));
MOCK_METHOD(int, GetPlaneFd, (int plane), (const, override));
MOCK_METHOD(void*, GetPlaneAddr, (int plane), (const, override));
MOCK_METHOD(uint64_t, GetId, (), (const, override));
};
static size_t GetFormatBpp(uint32_t drm_format) {
switch (drm_format) {
case DRM_FORMAT_BGR233:
case DRM_FORMAT_C8:
case DRM_FORMAT_R8:
case DRM_FORMAT_RGB332:
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YVU420:
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV21:
return 1;
case DRM_FORMAT_ABGR1555:
case DRM_FORMAT_ABGR4444:
case DRM_FORMAT_ARGB1555:
case DRM_FORMAT_ARGB4444:
case DRM_FORMAT_BGR565:
case DRM_FORMAT_BGRA4444:
case DRM_FORMAT_BGRA5551:
case DRM_FORMAT_BGRX4444:
case DRM_FORMAT_BGRX5551:
case DRM_FORMAT_GR88:
case DRM_FORMAT_P010:
case DRM_FORMAT_RG88:
case DRM_FORMAT_RGB565:
case DRM_FORMAT_RGBA4444:
case DRM_FORMAT_RGBA5551:
case DRM_FORMAT_RGBX4444:
case DRM_FORMAT_RGBX5551:
case DRM_FORMAT_UYVY:
case DRM_FORMAT_VYUY:
case DRM_FORMAT_XBGR1555:
case DRM_FORMAT_XBGR4444:
case DRM_FORMAT_XRGB1555:
case DRM_FORMAT_XRGB4444:
case DRM_FORMAT_YUYV:
case DRM_FORMAT_YVYU:
return 2;
case DRM_FORMAT_BGR888:
case DRM_FORMAT_RGB888:
return 3;
case DRM_FORMAT_ABGR2101010:
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_AYUV:
case DRM_FORMAT_BGRA1010102:
case DRM_FORMAT_BGRA8888:
case DRM_FORMAT_BGRX1010102:
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_RGBA1010102:
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_RGBX1010102:
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_XBGR2101010:
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_XRGB8888:
return 4;
}
LOG(ERROR) << "Unknown format: " << FormatToString(drm_format);
return 0;
}
class CameraBufferManagerImplTest : public ::testing::Test {
public:
CameraBufferManagerImplTest() = default;
CameraBufferManagerImplTest(const CameraBufferManagerImplTest&) = delete;
CameraBufferManagerImplTest& operator=(const CameraBufferManagerImplTest&) =
delete;
void SetUp() override {
auto mock_allocator = std::make_unique<MockAllocator>();
allocator_ = mock_allocator.get();
cbm_ = std::make_unique<CameraBufferManagerImpl>(std::move(mock_allocator));
}
void TearDown() override {
// Verify that gbm_device is properly tear down.
EXPECT_CALL(*allocator_, DestroyAllocator()).Times(1);
cbm_ = nullptr;
EXPECT_EQ(::testing::Mock::VerifyAndClear(allocator_), true);
}
int CreateFakeFd() { return open("/dev/null", 0); }
std::unique_ptr<camera_buffer_handle_t> CreateBuffer(
uint32_t buffer_id,
uint32_t drm_format,
uint32_t hal_pixel_format,
uint32_t width,
uint32_t height) {
std::unique_ptr<camera_buffer_handle_t> buffer(new camera_buffer_handle_t);
buffer->fds[0] = CreateFakeFd();
buffer->magic = kCameraBufferMagic;
buffer->buffer_id = buffer_id;
buffer->drm_format = drm_format;
buffer->hal_pixel_format = hal_pixel_format;
buffer->width = width;
buffer->height = height;
buffer->strides[0] = width * GetFormatBpp(drm_format);
buffer->offsets[0] = 0;
switch (drm_format) {
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV21:
case DRM_FORMAT_P010:
buffer->strides[1] = width * GetFormatBpp(drm_format);
buffer->offsets[1] = buffer->strides[0] * height;
break;
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YVU420:
buffer->strides[1] = width * GetFormatBpp(drm_format) / 2;
buffer->strides[2] = width * GetFormatBpp(drm_format) / 2;
buffer->offsets[1] = buffer->strides[0] * height;
buffer->offsets[2] =
buffer->offsets[1] + (buffer->strides[1] * height / 2);
break;
default:
// Single planar buffer.
break;
}
return buffer;
}
protected:
std::unique_ptr<CameraBufferManagerImpl> cbm_;
MockAllocator* allocator_;
};
TEST_F(CameraBufferManagerImplTest, AllocateTest) {
const uint32_t kBufferWidth = 1280, kBufferHeight = 720,
usage = GRALLOC_USAGE_FORCE_I420;
buffer_handle_t buffer_handle;
uint32_t stride;
// Allocate the buffer.
auto mock_bo = std::make_unique<MockBufferObject>();
auto* bo_ptr = mock_bo.get();
EXPECT_CALL(*allocator_,
CreateBo(kBufferWidth, kBufferHeight, DRM_FORMAT_YUV420,
GBM_BO_USE_SW_READ_OFTEN | GBM_BO_USE_SW_WRITE_OFTEN))
.Times(1)
.WillOnce(Return(ByMove(std::move(mock_bo))));
BufferDescriptor desc = {
.drm_format = DRM_FORMAT_YUV420,
.width = kBufferWidth,
.height = kBufferHeight,
.gbm_flags = GBM_BO_USE_SW_READ_OFTEN | GBM_BO_USE_SW_WRITE_OFTEN,
.num_planes = 3,
.format_modifier = DRM_FORMAT_MOD_INVALID,
.planes = {
{.offset = 0, .row_stride = 0},
{.offset = 0, .row_stride = 0},
{.offset = 0, .row_stride = 0},
}};
EXPECT_CALL(*bo_ptr, Describe()).Times(1).WillOnce(Return(desc));
for (size_t plane = 0; plane < 3; ++plane) {
EXPECT_CALL(*bo_ptr, GetPlaneFd(plane))
.Times(1)
.WillOnce(Return(CreateFakeFd()));
}
EXPECT_EQ(cbm_->Allocate(kBufferWidth, kBufferHeight,
HAL_PIXEL_FORMAT_YCbCr_420_888, usage,
&buffer_handle, &stride),
0);
// Lock the buffer. All the planes should be mapped.
for (size_t plane = 0; plane < 3; ++plane) {
EXPECT_CALL(*bo_ptr, BeginCpuAccess(A<SyncType>(), plane))
.WillOnce(Return(true));
EXPECT_CALL(*bo_ptr, Map(plane)).WillOnce(Return(true));
EXPECT_CALL(*bo_ptr, GetPlaneAddr(plane))
.Times(1)
.WillOnce((Return(fake_addr)));
}
struct android_ycbcr ycbcr;
EXPECT_EQ(cbm_->LockYCbCr(buffer_handle, 0, 0, 0, kBufferWidth, kBufferHeight,
&ycbcr),
0);
// Unlock the buffer. All the planes should be unmapped.
for (size_t plane = 0; plane < 3; ++plane) {
EXPECT_CALL(*bo_ptr, Unmap(plane));
EXPECT_CALL(*bo_ptr, EndCpuAccess(A<SyncType>(), plane))
.WillOnce(Return(true));
}
EXPECT_EQ(cbm_->Unlock(buffer_handle), 0);
// Free the buffer.
EXPECT_CALL(*bo_ptr, DestroyBo()).Times(1);
EXPECT_EQ(cbm_->Free(buffer_handle), 0);
}
TEST_F(CameraBufferManagerImplTest, LockTest) {
// Create a dummy buffer.
const int kBufferWidth = 1280, kBufferHeight = 720;
auto buffer = CreateBuffer(1, DRM_FORMAT_XBGR8888,
HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
kBufferWidth, kBufferHeight);
buffer_handle_t handle = reinterpret_cast<buffer_handle_t>(buffer.get());
// Register the buffer.
auto mock_bo = std::make_unique<MockBufferObject>();
auto* bo_ptr = mock_bo.get();
EXPECT_CALL(*allocator_, ImportBo(A<const ImportData&>()))
.Times(1)
.WillOnce(Return(ByMove(std::move(mock_bo))));
EXPECT_EQ(cbm_->Register(handle), 0);
// The call to Lock |handle| should succeed with valid width and height.
EXPECT_CALL(*bo_ptr, BeginCpuAccess(A<SyncType>(), 0))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(*bo_ptr, Map(0)).Times(1).WillOnce(Return(true));
EXPECT_CALL(*bo_ptr, GetPlaneAddr(0)).Times(1).WillOnce((Return(fake_addr)));
void* addr;
EXPECT_EQ(cbm_->Lock(handle, 0, 0, 0, kBufferWidth, kBufferHeight, &addr), 0);
EXPECT_EQ(addr, fake_addr);
// And the call to Unlock on |handle| should also succeed.
EXPECT_CALL(*bo_ptr, Unmap(0)).Times(1);
EXPECT_CALL(*bo_ptr, EndCpuAccess(A<SyncType>(), 0))
.Times(1)
.WillOnce(Return(true));
EXPECT_EQ(cbm_->Unlock(handle), 0);
// Finally the bo for |handle| should be unmapped and destroyed when we
// deregister the buffer.
EXPECT_CALL(*bo_ptr, DestroyBo()).Times(1);
EXPECT_EQ(cbm_->Deregister(handle), 0);
}
TEST_F(CameraBufferManagerImplTest, LockYCbCrTest) {
constexpr int kBufferWidth = 1280, kBufferHeight = 720;
{
// Create a dummy buffer.
auto buffer =
CreateBuffer(1, DRM_FORMAT_YUV420, HAL_PIXEL_FORMAT_YCbCr_420_888,
kBufferWidth, kBufferHeight);
buffer_handle_t handle = reinterpret_cast<buffer_handle_t>(buffer.get());
// Register the buffer.
auto mock_bo = std::make_unique<MockBufferObject>();
auto* bo_ptr = mock_bo.get();
EXPECT_CALL(*allocator_, ImportBo(A<const ImportData&>()))
.Times(1)
.WillOnce(Return(ByMove(std::move(mock_bo))));
EXPECT_EQ(cbm_->Register(handle), 0);
// The call to Lock |handle| should succeed with valid width and height.
for (size_t i = 0; i < 3; ++i) {
EXPECT_CALL(*bo_ptr, BeginCpuAccess(A<SyncType>(), i))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(*bo_ptr, Map(i)).Times(1).WillOnce(Return(true));
EXPECT_CALL(*bo_ptr, GetPlaneAddr(i))
.Times(1)
.WillOnce((Return(reinterpret_cast<uint8_t*>(fake_addr) +
buffer->offsets[i])));
}
struct android_ycbcr ycbcr;
EXPECT_EQ(
cbm_->LockYCbCr(handle, 0, 0, 0, kBufferWidth, kBufferHeight, &ycbcr),
0);
EXPECT_EQ(ycbcr.y, fake_addr);
EXPECT_EQ(ycbcr.cb,
reinterpret_cast<uint8_t*>(fake_addr) + buffer->offsets[1]);
EXPECT_EQ(ycbcr.cr,
reinterpret_cast<uint8_t*>(fake_addr) + buffer->offsets[2]);
EXPECT_EQ(ycbcr.ystride, buffer->strides[0]);
EXPECT_EQ(ycbcr.cstride, buffer->strides[1]);
EXPECT_EQ(ycbcr.chroma_step, 1);
// And the call to Unlock on |handle| should also succeed.
for (size_t i = 0; i < 3; ++i) {
EXPECT_CALL(*bo_ptr, Unmap(i)).Times(1);
EXPECT_CALL(*bo_ptr, EndCpuAccess(A<SyncType>(), i))
.Times(1)
.WillOnce(Return(true));
}
EXPECT_EQ(cbm_->Unlock(handle), 0);
// Finally the bo for |handle| should be unmapped and destroyed when we
// deregister the buffer.
EXPECT_CALL(*bo_ptr, DestroyBo()).Times(1);
EXPECT_EQ(cbm_->Deregister(handle), 0);
}
// Test semi-planar buffers with a list of (DRM_format, chroma_step).
std::vector<std::tuple<uint32_t, size_t>> formats_to_test = {
{DRM_FORMAT_NV12, 2}, {DRM_FORMAT_P010, 4}};
for (const auto& f : formats_to_test) {
auto buffer =
CreateBuffer(2, std::get<0>(f), HAL_PIXEL_FORMAT_YCbCr_420_888,
kBufferWidth, kBufferHeight);
buffer_handle_t handle = reinterpret_cast<buffer_handle_t>(buffer.get());
auto mock_bo = std::make_unique<MockBufferObject>();
auto* bo_ptr = mock_bo.get();
EXPECT_CALL(*allocator_, ImportBo(A<const ImportData&>()))
.Times(1)
.WillOnce(Return(ByMove(std::move(mock_bo))));
EXPECT_EQ(cbm_->Register(handle), 0);
for (size_t i = 0; i < 2; ++i) {
EXPECT_CALL(*bo_ptr, BeginCpuAccess(A<SyncType>(), i))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(*bo_ptr, Map(i)).Times(1).WillOnce(Return(true));
EXPECT_CALL(*bo_ptr, GetPlaneAddr(i))
.Times(1)
.WillOnce((Return(reinterpret_cast<uint8_t*>(fake_addr) +
buffer->offsets[i])));
}
struct android_ycbcr ycbcr;
EXPECT_EQ(
cbm_->LockYCbCr(handle, 0, 0, 0, kBufferWidth, kBufferHeight, &ycbcr),
0);
EXPECT_EQ(ycbcr.y, fake_addr);
EXPECT_EQ(ycbcr.cb,
reinterpret_cast<uint8_t*>(fake_addr) + buffer->offsets[1]);
EXPECT_EQ(ycbcr.cr, reinterpret_cast<uint8_t*>(fake_addr) +
buffer->offsets[1] + (std::get<1>(f) / 2));
EXPECT_EQ(ycbcr.ystride, buffer->strides[0]);
EXPECT_EQ(ycbcr.cstride, buffer->strides[1]);
EXPECT_EQ(ycbcr.chroma_step, std::get<1>(f));
for (size_t i = 0; i < 2; ++i) {
EXPECT_CALL(*bo_ptr, Unmap(i)).Times(1);
EXPECT_CALL(*bo_ptr, EndCpuAccess(A<SyncType>(), i))
.Times(1)
.WillOnce(Return(true));
}
EXPECT_EQ(cbm_->Unlock(handle), 0);
EXPECT_CALL(*bo_ptr, DestroyBo()).Times(1);
EXPECT_EQ(cbm_->Deregister(handle), 0);
}
}
TEST_F(CameraBufferManagerImplTest, GetPlaneSizeTest) {
const int kBufferWidth = 1280, kBufferHeight = 720;
auto gralloc_buffer = CreateBuffer(0, DRM_FORMAT_XBGR8888,
HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
kBufferWidth, kBufferHeight);
buffer_handle_t rgbx_handle =
reinterpret_cast<buffer_handle_t>(gralloc_buffer.get());
const size_t kRGBXBufferSize =
kBufferWidth * kBufferHeight * GetFormatBpp(DRM_FORMAT_XBGR8888);
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(rgbx_handle, 0),
kRGBXBufferSize);
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(rgbx_handle, 1), 0);
auto nv12_buffer =
CreateBuffer(1, DRM_FORMAT_NV21, HAL_PIXEL_FORMAT_YCbCr_420_888,
kBufferWidth, kBufferHeight);
const size_t kNV12Plane0Size =
kBufferWidth * kBufferHeight * GetFormatBpp(DRM_FORMAT_NV12);
const size_t kNV12Plane1Size =
kBufferWidth * kBufferHeight * GetFormatBpp(DRM_FORMAT_NV12) / 2;
buffer_handle_t nv12_handle =
reinterpret_cast<buffer_handle_t>(nv12_buffer.get());
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(nv12_handle, 0),
kNV12Plane0Size);
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(nv12_handle, 1),
kNV12Plane1Size);
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(nv12_handle, 2), 0);
auto yuv420_buffer =
CreateBuffer(2, DRM_FORMAT_YUV420, HAL_PIXEL_FORMAT_YCbCr_420_888,
kBufferWidth, kBufferHeight);
const size_t kYuv420Plane0Size =
kBufferWidth * kBufferHeight * GetFormatBpp(DRM_FORMAT_YUV420);
const size_t kYuv420Plane12Size =
kBufferWidth * kBufferHeight * GetFormatBpp(DRM_FORMAT_YUV420) / 4;
buffer_handle_t yuv420_handle =
reinterpret_cast<buffer_handle_t>(yuv420_buffer.get());
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(yuv420_handle, 0),
kYuv420Plane0Size);
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(yuv420_handle, 1),
kYuv420Plane12Size);
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(yuv420_handle, 2),
kYuv420Plane12Size);
EXPECT_EQ(CameraBufferManagerImpl::GetPlaneSize(yuv420_handle, 3), 0);
}
TEST_F(CameraBufferManagerImplTest, IsValidBufferTest) {
const int kBufferWidth = 1280, kBufferHeight = 720;
EXPECT_FALSE(CameraBufferManagerImpl::IsValidBuffer(nullptr));
auto cbh = CreateBuffer(2, DRM_FORMAT_NV12, HAL_PIXEL_FORMAT_YCbCr_420_888,
kBufferWidth, kBufferHeight);
buffer_handle_t handle = reinterpret_cast<buffer_handle_t>(cbh.get());
EXPECT_TRUE(CameraBufferManagerImpl::IsValidBuffer(handle));
cbh->magic = ~cbh->magic;
EXPECT_FALSE(CameraBufferManagerImpl::IsValidBuffer(handle));
}
TEST_F(CameraBufferManagerImplTest, DeregisterTest) {
// Create two dummy buffers.
const int kBufferWidth = 1280, kBufferHeight = 720;
auto buffer1 =
CreateBuffer(1, DRM_FORMAT_YUV420, HAL_PIXEL_FORMAT_YCbCr_420_888,
kBufferWidth, kBufferHeight);
buffer_handle_t handle1 = reinterpret_cast<buffer_handle_t>(buffer1.get());
auto buffer2 =
CreateBuffer(1, DRM_FORMAT_YUV420, HAL_PIXEL_FORMAT_YCbCr_420_888,
kBufferWidth, kBufferHeight);
buffer_handle_t handle2 = reinterpret_cast<buffer_handle_t>(buffer2.get());
// Register the buffers.
auto mock_bo1 = std::make_unique<MockBufferObject>();
auto* bo1_ptr = mock_bo1.get();
EXPECT_CALL(*allocator_, ImportBo(A<const ImportData&>()))
.Times(1)
.WillOnce(Return(ByMove(std::move(mock_bo1))));
EXPECT_EQ(cbm_->Register(handle1), 0);
auto mock_bo2 = std::make_unique<MockBufferObject>();
auto* bo2_ptr = mock_bo2.get();
EXPECT_CALL(*allocator_, ImportBo(A<const ImportData&>()))
.Times(1)
.WillOnce(Return(ByMove(std::move(mock_bo2))));
EXPECT_EQ(cbm_->Register(handle2), 0);
// Lock both buffers
struct android_ycbcr ycbcr;
for (size_t i = 0; i < 3; ++i) {
EXPECT_CALL(*bo1_ptr, BeginCpuAccess(A<SyncType>(), i))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(*bo1_ptr, Map(i)).Times(1).WillOnce(Return(true));
EXPECT_CALL(*bo1_ptr, GetPlaneAddr(i))
.Times(1)
.WillOnce((Return(reinterpret_cast<uint8_t*>(fake_addr) +
buffer1->offsets[i])));
}
EXPECT_EQ(
cbm_->LockYCbCr(handle1, 0, 0, 0, kBufferWidth, kBufferHeight, &ycbcr),
0);
for (size_t i = 0; i < 3; ++i) {
EXPECT_CALL(*bo2_ptr, BeginCpuAccess(A<SyncType>(), i))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(*bo2_ptr, Map(i)).Times(1).WillOnce(Return(true));
EXPECT_CALL(*bo2_ptr, GetPlaneAddr(i))
.Times(1)
.WillOnce((Return(reinterpret_cast<uint8_t*>(fake_addr) +
buffer2->offsets[i])));
}
EXPECT_EQ(
cbm_->LockYCbCr(handle2, 0, 0, 0, kBufferWidth, kBufferHeight, &ycbcr),
0);
EXPECT_CALL(*bo1_ptr, DestroyBo()).Times(1);
EXPECT_EQ(cbm_->Deregister(handle1), 0);
EXPECT_CALL(*bo2_ptr, DestroyBo()).Times(1);
EXPECT_EQ(cbm_->Deregister(handle2), 0);
}
} // namespace cros::tests
int main(int argc, char** argv) {
base::AtExitManager exit_manager;
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}