/* * 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 #include #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 char kCameraId[] = "42"; 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 kDefaultDeviceId = 0; 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&), (override)); MOCK_METHOD(ndk::ScopedAStatus, processCaptureResult, (const std::vector&), (override)); MOCK_METHOD(ndk::ScopedAStatus, requestStreamBuffers, (const std::vector&, std::vector*, BufferRequestStatus*), (override)); MOCK_METHOD(ndk::ScopedAStatus, returnStreamBuffers, (const std::vector&), (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(); mMockVirtualCameraClientCallback = ndk::SharedRefBase::make(); // 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 mMockCameraDeviceCallback; std::shared_ptr mMockVirtualCameraClientCallback; }; class VirtualCameraSessionTest : public VirtualCameraSessionTestBase { public: void SetUp() override { VirtualCameraSessionTestBase::SetUp(); mVirtualCameraDevice = ndk::SharedRefBase::make( 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}, kDefaultDeviceId); mVirtualCameraSession = ndk::SharedRefBase::make( mVirtualCameraDevice, mMockCameraDeviceCallback, mMockVirtualCameraClientCallback); } protected: std::shared_ptr mVirtualCameraDevice; std::shared_ptr 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 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 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 requests(1); requests[0].frameNumber = 42; requests[0].settings = *( MetadataBuilder().setControlAfMode(ANDROID_CONTROL_AF_MODE_AUTO).build()); std::vector 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 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(Status::CAMERA_DISCONNECTED))); } TEST_F(VirtualCameraSessionTest, ConfigureWithEmptyStreams) { StreamConfiguration streamConfiguration; std::vector halStreams; // Expect configuration attempt returns CAMERA_DISCONNECTED service specific code. EXPECT_THAT( mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams) .getServiceSpecificError(), Eq(static_cast(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 halStreams; // Expect configuration attempt returns CAMERA_DISCONNECTED service specific code. EXPECT_THAT( mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams) .getServiceSpecificError(), Eq(static_cast(Status::ILLEGAL_ARGUMENT))); } class VirtualCameraSessionInputChoiceTest : public VirtualCameraSessionTestBase { public: std::shared_ptr createSession( const std::vector& supportedInputConfigs) { mVirtualCameraDevice = ndk::SharedRefBase::make( kCameraId, VirtualCameraConfiguration{ .supportedStreamConfigs = supportedInputConfigs, .virtualCameraCallback = mMockVirtualCameraClientCallback, .sensorOrientation = SensorOrientation::ORIENTATION_0, .lensFacing = LensFacing::FRONT}, kDefaultDeviceId); return ndk::SharedRefBase::make( mVirtualCameraDevice, mMockCameraDeviceCallback, mMockVirtualCameraClientCallback); } protected: std::shared_ptr 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 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 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()); } TEST_F(VirtualCameraSessionInputChoiceTest, reconfigureSwitchesInputStream) { // 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}}); // First configure QVGA stream. StreamConfiguration streamConfiguration; streamConfiguration.streams = {createStream( kStreamId, kQvgaWidth, kQvgaHeight, PixelFormat::IMPLEMENTATION_DEFINED)}; std::vector halStreams; // Expect QVGA input configuragion to be chosen. EXPECT_CALL(*mMockVirtualCameraClientCallback, onStreamConfigured(kStreamId, _, kQvgaWidth, kQvgaHeight, Format::RGBA_8888)); EXPECT_TRUE( virtualCameraSession->configureStreams(streamConfiguration, &halStreams) .isOk()); // Reconfigure with additional VGA stream. streamConfiguration.streams.push_back( createStream(kStreamId + 1, kVgaWidth, kVgaHeight, PixelFormat::IMPLEMENTATION_DEFINED)); // Expect original surface to be discarded. EXPECT_CALL(*mMockVirtualCameraClientCallback, onStreamClosed(kStreamId)); // Expect SVGA input configuragion to be chosen. EXPECT_CALL(*mMockVirtualCameraClientCallback, onStreamConfigured(kStreamId + 1, _, kSvgaWidth, kSvgaHeight, Format::YUV_420_888)); EXPECT_TRUE( virtualCameraSession->configureStreams(streamConfiguration, &halStreams) .isOk()); } TEST_F(VirtualCameraSessionInputChoiceTest, reconfigureKeepsInputStreamIfUnchanged) { // 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}}); // First configure SVGA stream. StreamConfiguration streamConfiguration; streamConfiguration.streams = {createStream( kStreamId, kSvgaWidth, kSvgaHeight, PixelFormat::IMPLEMENTATION_DEFINED)}; std::vector halStreams; // Expect SVGA input configuragion to be chosen. EXPECT_CALL(*mMockVirtualCameraClientCallback, onStreamConfigured(kStreamId, _, kSvgaWidth, kSvgaHeight, Format::YUV_420_888)); EXPECT_TRUE( virtualCameraSession->configureStreams(streamConfiguration, &halStreams) .isOk()); // Reconfigure with VGA + QVA stream. Because we only allow downscaling, // this will be matched to SVGA input resolution. streamConfiguration.streams = { createStream(kStreamId + 1, kVgaWidth, kVgaHeight, PixelFormat::IMPLEMENTATION_DEFINED), createStream(kStreamId + 2, kVgaWidth, kVgaHeight, PixelFormat::IMPLEMENTATION_DEFINED)}; // Expect the onStreamConfigured callback not to be invoked, since the // original Surface is still best fit for current output streams. EXPECT_CALL(*mMockVirtualCameraClientCallback, onStreamConfigured).Times(0); EXPECT_TRUE( virtualCameraSession->configureStreams(streamConfiguration, &halStreams) .isOk()); } } // namespace } // namespace virtualcamera } // namespace companion } // namespace android