blob: 5f313a0677cd58d4f943e51a39f56cf446c2d010 [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cstdint>
#include <memory>
#include "VirtualCameraDevice.h"
#include "VirtualCameraSession.h"
#include "aidl/android/companion/virtualcamera/BnVirtualCameraCallback.h"
#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
#include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
#include "aidl/android/hardware/camera/common/Status.h"
#include "aidl/android/hardware/camera/device/BnCameraDeviceCallback.h"
#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
#include "aidl/android/hardware/graphics/common/PixelFormat.h"
#include "android/binder_auto_utils.h"
#include "android/binder_interface_utils.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "util/MetadataUtil.h"
namespace android {
namespace companion {
namespace virtualcamera {
namespace {
constexpr int kQvgaWidth = 320;
constexpr int kQvgaHeight = 240;
constexpr int kVgaWidth = 640;
constexpr int kVgaHeight = 480;
constexpr int kSvgaWidth = 800;
constexpr int kSvgaHeight = 600;
constexpr int kMaxFps = 30;
constexpr int kStreamId = 0;
constexpr int kSecondStreamId = 1;
constexpr int kCameraId = 42;
using ::aidl::android::companion::virtualcamera::BnVirtualCameraCallback;
using ::aidl::android::companion::virtualcamera::Format;
using ::aidl::android::companion::virtualcamera::LensFacing;
using ::aidl::android::companion::virtualcamera::SensorOrientation;
using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
using ::aidl::android::hardware::camera::common::Status;
using ::aidl::android::hardware::camera::device::BnCameraDeviceCallback;
using ::aidl::android::hardware::camera::device::BufferRequest;
using ::aidl::android::hardware::camera::device::BufferRequestStatus;
using ::aidl::android::hardware::camera::device::CaptureRequest;
using ::aidl::android::hardware::camera::device::CaptureResult;
using ::aidl::android::hardware::camera::device::HalStream;
using ::aidl::android::hardware::camera::device::NotifyMsg;
using ::aidl::android::hardware::camera::device::Stream;
using ::aidl::android::hardware::camera::device::StreamBuffer;
using ::aidl::android::hardware::camera::device::StreamBufferRet;
using ::aidl::android::hardware::camera::device::StreamConfiguration;
using ::aidl::android::hardware::graphics::common::PixelFormat;
using ::aidl::android::view::Surface;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Return;
using ::testing::SizeIs;
Stream createStream(int streamId, int width, int height, PixelFormat format) {
Stream s;
s.id = streamId;
s.width = width;
s.height = height;
s.format = format;
return s;
}
class MockCameraDeviceCallback : public BnCameraDeviceCallback {
public:
MOCK_METHOD(ndk::ScopedAStatus, notify, (const std::vector<NotifyMsg>&),
(override));
MOCK_METHOD(ndk::ScopedAStatus, processCaptureResult,
(const std::vector<CaptureResult>&), (override));
MOCK_METHOD(ndk::ScopedAStatus, requestStreamBuffers,
(const std::vector<BufferRequest>&, std::vector<StreamBufferRet>*,
BufferRequestStatus*),
(override));
MOCK_METHOD(ndk::ScopedAStatus, returnStreamBuffers,
(const std::vector<StreamBuffer>&), (override));
};
class MockVirtualCameraCallback : public BnVirtualCameraCallback {
public:
MOCK_METHOD(ndk::ScopedAStatus, onStreamConfigured,
(int, const Surface&, int32_t, int32_t, Format), (override));
MOCK_METHOD(ndk::ScopedAStatus, onProcessCaptureRequest, (int, int),
(override));
MOCK_METHOD(ndk::ScopedAStatus, onStreamClosed, (int), (override));
};
class VirtualCameraSessionTestBase : public ::testing::Test {
public:
virtual void SetUp() override {
mMockCameraDeviceCallback =
ndk::SharedRefBase::make<MockCameraDeviceCallback>();
mMockVirtualCameraClientCallback =
ndk::SharedRefBase::make<MockVirtualCameraCallback>();
// Explicitly defining default actions below to prevent gmock from
// default-constructing ndk::ScopedAStatus, because default-constructed
// status wraps nullptr AStatus and causes crash when attempting to print
// it in gtest report.
ON_CALL(*mMockCameraDeviceCallback, notify)
.WillByDefault(ndk::ScopedAStatus::ok);
ON_CALL(*mMockCameraDeviceCallback, processCaptureResult)
.WillByDefault(ndk::ScopedAStatus::ok);
ON_CALL(*mMockCameraDeviceCallback, requestStreamBuffers)
.WillByDefault(ndk::ScopedAStatus::ok);
ON_CALL(*mMockCameraDeviceCallback, returnStreamBuffers)
.WillByDefault(ndk::ScopedAStatus::ok);
ON_CALL(*mMockVirtualCameraClientCallback, onStreamConfigured)
.WillByDefault(ndk::ScopedAStatus::ok);
ON_CALL(*mMockVirtualCameraClientCallback, onProcessCaptureRequest)
.WillByDefault(ndk::ScopedAStatus::ok);
ON_CALL(*mMockVirtualCameraClientCallback, onStreamClosed)
.WillByDefault(ndk::ScopedAStatus::ok);
}
protected:
std::shared_ptr<MockCameraDeviceCallback> mMockCameraDeviceCallback;
std::shared_ptr<MockVirtualCameraCallback> mMockVirtualCameraClientCallback;
};
class VirtualCameraSessionTest : public VirtualCameraSessionTestBase {
public:
void SetUp() override {
VirtualCameraSessionTestBase::SetUp();
mVirtualCameraDevice = ndk::SharedRefBase::make<VirtualCameraDevice>(
kCameraId,
VirtualCameraConfiguration{
.supportedStreamConfigs = {SupportedStreamConfiguration{
.width = kVgaWidth,
.height = kVgaHeight,
.pixelFormat = Format::YUV_420_888,
.maxFps = kMaxFps},
SupportedStreamConfiguration{
.width = kSvgaWidth,
.height = kSvgaHeight,
.pixelFormat = Format::YUV_420_888,
.maxFps = kMaxFps}},
.virtualCameraCallback = mMockVirtualCameraClientCallback,
.sensorOrientation = SensorOrientation::ORIENTATION_0,
.lensFacing = LensFacing::FRONT});
mVirtualCameraSession = ndk::SharedRefBase::make<VirtualCameraSession>(
mVirtualCameraDevice, mMockCameraDeviceCallback,
mMockVirtualCameraClientCallback);
}
protected:
std::shared_ptr<VirtualCameraDevice> mVirtualCameraDevice;
std::shared_ptr<VirtualCameraSession> mVirtualCameraSession;
};
TEST_F(VirtualCameraSessionTest, ConfigureTriggersClientConfigureCallback) {
PixelFormat format = PixelFormat::YCBCR_420_888;
StreamConfiguration streamConfiguration;
streamConfiguration.streams = {
createStream(kStreamId, kVgaWidth, kVgaHeight, format),
createStream(kSecondStreamId, kSvgaWidth, kSvgaHeight, format)};
std::vector<HalStream> halStreams;
// Expect highest resolution to be picked for the client input.
EXPECT_CALL(*mMockVirtualCameraClientCallback,
onStreamConfigured(kStreamId, _, kSvgaWidth, kSvgaHeight,
Format::YUV_420_888));
ASSERT_TRUE(
mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.isOk());
EXPECT_THAT(halStreams, SizeIs(streamConfiguration.streams.size()));
EXPECT_THAT(mVirtualCameraSession->getStreamIds(),
ElementsAre(kStreamId, kSecondStreamId));
}
TEST_F(VirtualCameraSessionTest, SecondConfigureDropsUnreferencedStreams) {
PixelFormat format = PixelFormat::YCBCR_420_888;
StreamConfiguration streamConfiguration;
std::vector<HalStream> halStreams;
streamConfiguration.streams = {createStream(0, kVgaWidth, kVgaHeight, format),
createStream(1, kVgaWidth, kVgaHeight, format),
createStream(2, kVgaWidth, kVgaHeight, format)};
ASSERT_TRUE(
mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.isOk());
EXPECT_THAT(mVirtualCameraSession->getStreamIds(), ElementsAre(0, 1, 2));
streamConfiguration.streams = {createStream(0, kVgaWidth, kVgaHeight, format),
createStream(2, kVgaWidth, kVgaHeight, format),
createStream(3, kVgaWidth, kVgaHeight, format)};
ASSERT_TRUE(
mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.isOk());
EXPECT_THAT(mVirtualCameraSession->getStreamIds(), ElementsAre(0, 2, 3));
}
TEST_F(VirtualCameraSessionTest, CloseTriggersClientTerminateCallback) {
EXPECT_CALL(*mMockVirtualCameraClientCallback, onStreamClosed(kStreamId))
.WillOnce(Return(ndk::ScopedAStatus::ok()));
ASSERT_TRUE(mVirtualCameraSession->close().isOk());
}
TEST_F(VirtualCameraSessionTest, FlushBeforeConfigure) {
// Flush request coming before the configure request finished
// (so potentially the thread is not yet running) should be
// gracefully handled.
EXPECT_TRUE(mVirtualCameraSession->flush().isOk());
}
TEST_F(VirtualCameraSessionTest, onProcessCaptureRequestTriggersClientCallback) {
StreamConfiguration streamConfiguration;
streamConfiguration.streams = {createStream(kStreamId, kVgaWidth, kVgaHeight,
PixelFormat::YCBCR_420_888)};
std::vector<CaptureRequest> requests(1);
requests[0].frameNumber = 42;
requests[0].settings = *(
MetadataBuilder().setControlAfMode(ANDROID_CONTROL_AF_MODE_AUTO).build());
std::vector<HalStream> halStreams;
ASSERT_TRUE(
mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.isOk());
EXPECT_CALL(*mMockVirtualCameraClientCallback,
onProcessCaptureRequest(kStreamId, requests[0].frameNumber))
.WillOnce(Return(ndk::ScopedAStatus::ok()));
int32_t aidlReturn = 0;
ASSERT_TRUE(mVirtualCameraSession
->processCaptureRequest(requests, /*in_cachesToRemove=*/{},
&aidlReturn)
.isOk());
EXPECT_THAT(aidlReturn, Eq(requests.size()));
}
TEST_F(VirtualCameraSessionTest, configureAfterCameraRelease) {
StreamConfiguration streamConfiguration;
streamConfiguration.streams = {createStream(kStreamId, kVgaWidth, kVgaHeight,
PixelFormat::YCBCR_420_888)};
std::vector<HalStream> halStreams;
// Release virtual camera.
mVirtualCameraDevice.reset();
// Expect configuration attempt returns CAMERA_DISCONNECTED service specific code.
EXPECT_THAT(
mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.getServiceSpecificError(),
Eq(static_cast<int32_t>(Status::CAMERA_DISCONNECTED)));
}
TEST_F(VirtualCameraSessionTest, ConfigureWithEmptyStreams) {
StreamConfiguration streamConfiguration;
std::vector<HalStream> halStreams;
// Expect configuration attempt returns CAMERA_DISCONNECTED service specific code.
EXPECT_THAT(
mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.getServiceSpecificError(),
Eq(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT)));
}
TEST_F(VirtualCameraSessionTest, ConfigureWithDifferentAspectRatioFails) {
StreamConfiguration streamConfiguration;
streamConfiguration.streams = {
createStream(kStreamId, kVgaWidth, kVgaHeight, PixelFormat::YCBCR_420_888),
createStream(kSecondStreamId, kVgaHeight, kVgaWidth,
PixelFormat::YCBCR_420_888)};
std::vector<HalStream> halStreams;
// Expect configuration attempt returns CAMERA_DISCONNECTED service specific code.
EXPECT_THAT(
mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.getServiceSpecificError(),
Eq(static_cast<int32_t>(Status::ILLEGAL_ARGUMENT)));
}
class VirtualCameraSessionInputChoiceTest : public VirtualCameraSessionTestBase {
public:
std::shared_ptr<VirtualCameraSession> createSession(
const std::vector<SupportedStreamConfiguration>& supportedInputConfigs) {
mVirtualCameraDevice = ndk::SharedRefBase::make<VirtualCameraDevice>(
kCameraId, VirtualCameraConfiguration{
.supportedStreamConfigs = supportedInputConfigs,
.virtualCameraCallback = mMockVirtualCameraClientCallback,
.sensorOrientation = SensorOrientation::ORIENTATION_0,
.lensFacing = LensFacing::FRONT});
return ndk::SharedRefBase::make<VirtualCameraSession>(
mVirtualCameraDevice, mMockCameraDeviceCallback,
mMockVirtualCameraClientCallback);
}
protected:
std::shared_ptr<VirtualCameraDevice> mVirtualCameraDevice;
};
TEST_F(VirtualCameraSessionInputChoiceTest,
configureChoosesCorrectInputStreamForDownsampledOutput) {
// Create camera configured to support SVGA YUV input and RGB QVGA input.
auto virtualCameraSession = createSession(
{SupportedStreamConfiguration{.width = kSvgaWidth,
.height = kSvgaHeight,
.pixelFormat = Format::YUV_420_888,
.maxFps = kMaxFps},
SupportedStreamConfiguration{.width = kQvgaWidth,
.height = kQvgaHeight,
.pixelFormat = Format::RGBA_8888,
.maxFps = kMaxFps}});
// Configure VGA stream. Expect SVGA input to be chosen to downscale from.
StreamConfiguration streamConfiguration;
streamConfiguration.streams = {createStream(
kStreamId, kVgaWidth, kVgaHeight, PixelFormat::IMPLEMENTATION_DEFINED)};
std::vector<HalStream> halStreams;
// Expect configuration attempt returns CAMERA_DISCONNECTED service specific code.
EXPECT_CALL(*mMockVirtualCameraClientCallback,
onStreamConfigured(kStreamId, _, kSvgaWidth, kSvgaHeight,
Format::YUV_420_888));
EXPECT_TRUE(
virtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.isOk());
}
TEST_F(VirtualCameraSessionInputChoiceTest,
configureChoosesCorrectInputStreamForMatchingResolution) {
// Create camera configured to support SVGA YUV input and RGB QVGA input.
auto virtualCameraSession = createSession(
{SupportedStreamConfiguration{.width = kSvgaWidth,
.height = kSvgaHeight,
.pixelFormat = Format::YUV_420_888,
.maxFps = kMaxFps},
SupportedStreamConfiguration{.width = kQvgaWidth,
.height = kQvgaHeight,
.pixelFormat = Format::RGBA_8888,
.maxFps = kMaxFps}});
// Configure VGA stream. Expect SVGA input to be chosen to downscale from.
StreamConfiguration streamConfiguration;
streamConfiguration.streams = {createStream(
kStreamId, kQvgaWidth, kQvgaHeight, PixelFormat::IMPLEMENTATION_DEFINED)};
std::vector<HalStream> halStreams;
// Expect configuration attempt returns CAMERA_DISCONNECTED service specific code.
EXPECT_CALL(*mMockVirtualCameraClientCallback,
onStreamConfigured(kStreamId, _, kQvgaWidth, kQvgaHeight,
Format::RGBA_8888));
EXPECT_TRUE(
virtualCameraSession->configureStreams(streamConfiguration, &halStreams)
.isOk());
}
} // namespace
} // namespace virtualcamera
} // namespace companion
} // namespace android