/* * Copyright (C) 2022 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 "FrameHandler.h" #include "FrameHandlerUltrasonics.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // These values are called out in the EVS design doc (as of Mar 8, 2017) constexpr int kMaxStreamStartMilliseconds = 500; constexpr int kMinimumFramesPerSecond = 10; constexpr int kSecondsToMilliseconds = 1000; constexpr int kMillisecondsToMicroseconds = 1000; constexpr float kNanoToMilliseconds = 0.000001f; constexpr float kNanoToSeconds = 0.000000001f; /* * Please note that this is different from what is defined in * libhardware/modules/camera/3_4/metadata/types.h; this has one additional * field to store a framerate. */ typedef struct { int32_t id; int32_t width; int32_t height; int32_t format; int32_t direction; int32_t framerate; } RawStreamConfig; constexpr size_t kStreamCfgSz = sizeof(RawStreamConfig) / sizeof(int32_t); using ::aidl::android::hardware::automotive::evs::BnEvsEnumeratorStatusCallback; using ::aidl::android::hardware::automotive::evs::BufferDesc; using ::aidl::android::hardware::automotive::evs::CameraDesc; using ::aidl::android::hardware::automotive::evs::CameraParam; using ::aidl::android::hardware::automotive::evs::DeviceStatus; using ::aidl::android::hardware::automotive::evs::DisplayDesc; using ::aidl::android::hardware::automotive::evs::DisplayState; using ::aidl::android::hardware::automotive::evs::EvsEventDesc; using ::aidl::android::hardware::automotive::evs::EvsEventType; using ::aidl::android::hardware::automotive::evs::EvsResult; using ::aidl::android::hardware::automotive::evs::IEvsCamera; using ::aidl::android::hardware::automotive::evs::IEvsDisplay; using ::aidl::android::hardware::automotive::evs::IEvsEnumerator; using ::aidl::android::hardware::automotive::evs::IEvsEnumeratorStatusCallback; using ::aidl::android::hardware::automotive::evs::IEvsUltrasonicsArray; using ::aidl::android::hardware::automotive::evs::ParameterRange; using ::aidl::android::hardware::automotive::evs::Stream; using ::aidl::android::hardware::automotive::evs::UltrasonicsArrayDesc; using ::aidl::android::hardware::graphics::common::BufferUsage; using ::aidl::android::hardware::graphics::common::HardwareBufferDescription; using ::aidl::android::hardware::graphics::common::PixelFormat; using std::chrono_literals::operator""s; } // namespace // The main test class for EVS class EvsAidlTest : public ::testing::TestWithParam { public: virtual void SetUp() override { // Make sure we can connect to the enumerator std::string service_name = GetParam(); AIBinder* binder = AServiceManager_waitForService(service_name.data()); ASSERT_NE(binder, nullptr); mEnumerator = IEvsEnumerator::fromBinder(::ndk::SpAIBinder(binder)); LOG(INFO) << "Test target service: " << service_name; ASSERT_TRUE(mEnumerator->isHardware(&mIsHwModule).isOk()); } virtual void TearDown() override { // Attempt to close any active camera for (auto&& cam : mActiveCameras) { if (cam != nullptr) { mEnumerator->closeCamera(cam); } } mActiveCameras.clear(); } protected: void loadCameraList() { // SetUp() must run first! ASSERT_NE(mEnumerator, nullptr); // Get the camera list ASSERT_TRUE(mEnumerator->getCameraList(&mCameraInfo).isOk()) << "Failed to get a list of available cameras"; LOG(INFO) << "We have " << mCameraInfo.size() << " cameras."; } void loadUltrasonicsArrayList() { // SetUp() must run first! ASSERT_NE(mEnumerator, nullptr); // Get the ultrasonics array list auto result = mEnumerator->getUltrasonicsArrayList(&mUltrasonicsArraysInfo); ASSERT_TRUE(result.isOk() || // TODO(b/149874793): Remove below conditions when // getUltrasonicsArrayList() is implemented. (!result.isOk() && result.getServiceSpecificError() == static_cast(EvsResult::NOT_IMPLEMENTED))) << "Failed to get a list of available ultrasonics arrays"; LOG(INFO) << "We have " << mCameraInfo.size() << " ultrasonics arrays."; } bool isLogicalCamera(const camera_metadata_t* metadata) { if (metadata == nullptr) { // A logical camera device must have a valid camera metadata. return false; } // Looking for LOGICAL_MULTI_CAMERA capability from metadata. camera_metadata_ro_entry_t entry; int rc = find_camera_metadata_ro_entry(metadata, ANDROID_REQUEST_AVAILABLE_CAPABILITIES, &entry); if (rc != 0) { // No capabilities are found. return false; } for (size_t i = 0; i < entry.count; ++i) { uint8_t cap = entry.data.u8[i]; if (cap == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) { return true; } } return false; } std::unordered_set getPhysicalCameraIds(const std::string& id, bool& flag) { std::unordered_set physicalCameras; const auto it = std::find_if(mCameraInfo.begin(), mCameraInfo.end(), [&id](const CameraDesc& desc) { return id == desc.id; }); if (it == mCameraInfo.end()) { // Unknown camera is requested. Return an empty list. return physicalCameras; } const camera_metadata_t* metadata = reinterpret_cast(&it->metadata[0]); flag = isLogicalCamera(metadata); if (!flag) { // EVS assumes that the device w/o a valid metadata is a physical // device. LOG(INFO) << id << " is not a logical camera device."; physicalCameras.insert(id); return physicalCameras; } // Look for physical camera identifiers camera_metadata_ro_entry entry; int rc = find_camera_metadata_ro_entry(metadata, ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS, &entry); if (rc != 0) { LOG(ERROR) << "No physical camera ID is found for a logical camera device"; } const uint8_t* ids = entry.data.u8; size_t start = 0; for (size_t i = 0; i < entry.count; ++i) { if (ids[i] == '\0') { if (start != i) { std::string id(reinterpret_cast(ids + start)); physicalCameras.insert(id); } start = i + 1; } } LOG(INFO) << id << " consists of " << physicalCameras.size() << " physical camera devices"; return physicalCameras; } Stream getFirstStreamConfiguration(camera_metadata_t* metadata) { Stream targetCfg = {}; camera_metadata_entry_t streamCfgs; if (!find_camera_metadata_entry(metadata, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &streamCfgs)) { // Stream configurations are found in metadata RawStreamConfig* ptr = reinterpret_cast(streamCfgs.data.i32); for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) { if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) { targetCfg.width = ptr->width; targetCfg.height = ptr->height; targetCfg.format = static_cast(ptr->format); break; } ++ptr; } } return targetCfg; } class DeviceStatusCallback : public BnEvsEnumeratorStatusCallback { ndk::ScopedAStatus deviceStatusChanged(const std::vector&) override { // This empty implementation returns always ok(). return ndk::ScopedAStatus::ok(); } }; // Every test needs access to the service std::shared_ptr mEnumerator; // Empty unless/util loadCameraList() is called std::vector mCameraInfo; // boolean to tell current module under testing is HW module implementation // or not bool mIsHwModule; // A list of active camera handles that are need to be cleaned up std::deque> mActiveCameras; // Empty unless/util loadUltrasonicsArrayList() is called std::vector mUltrasonicsArraysInfo; // A list of active ultrasonics array handles that are to be cleaned up std::deque> mActiveUltrasonicsArrays; }; // Test cases, their implementations, and corresponding requirements are // documented at go/aae-evs-public-api-test. /* * CameraOpenClean: * Opens each camera reported by the enumerator and then explicitly closes it via a * call to closeCamera. Then repeats the test to ensure all cameras can be reopened. */ TEST_P(EvsAidlTest, CameraOpenClean) { LOG(INFO) << "Starting CameraOpenClean test"; // Get the camera list loadCameraList(); // Open and close each camera twice for (auto&& cam : mCameraInfo) { bool isLogicalCam = false; auto devices = getPhysicalCameraIds(cam.id, isLogicalCam); if (mIsHwModule && isLogicalCam) { LOG(INFO) << "Skip a logical device, " << cam.id << " for HW target."; continue; } // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); for (int pass = 0; pass < 2; pass++) { std::shared_ptr pCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam).isOk()); ASSERT_NE(pCam, nullptr); CameraDesc cameraInfo; for (auto&& devName : devices) { ASSERT_TRUE(pCam->getPhysicalCameraInfo(devName, &cameraInfo).isOk()); EXPECT_EQ(devName, cameraInfo.id); } // Store a camera handle for a clean-up mActiveCameras.push_back(pCam); // Verify that this camera self-identifies correctly ASSERT_TRUE(pCam->getCameraInfo(&cameraInfo).isOk()); EXPECT_EQ(cam.id, cameraInfo.id); // Verify methods for extended info const auto id = 0xFFFFFFFF; // meaningless id std::vector values; auto status = pCam->setExtendedInfo(id, values); if (isLogicalCam) { EXPECT_TRUE(!status.isOk() && status.getServiceSpecificError() == static_cast(EvsResult::NOT_SUPPORTED)); } else { EXPECT_TRUE(status.isOk()); } status = pCam->getExtendedInfo(id, &values); if (isLogicalCam) { EXPECT_TRUE(!status.isOk() && status.getServiceSpecificError() == static_cast(EvsResult::NOT_SUPPORTED)); } else { EXPECT_TRUE(status.isOk()); } // Explicitly close the camera so resources are released right away ASSERT_TRUE(mEnumerator->closeCamera(pCam).isOk()); mActiveCameras.clear(); } } } /* * CameraOpenAggressive: * Opens each camera reported by the enumerator twice in a row without an intervening closeCamera * call. This ensures that the intended "aggressive open" behavior works. This is necessary for * the system to be tolerant of shutdown/restart race conditions. */ TEST_P(EvsAidlTest, CameraOpenAggressive) { LOG(INFO) << "Starting CameraOpenAggressive test"; // Get the camera list loadCameraList(); // Open and close each camera twice for (auto&& cam : mCameraInfo) { bool isLogicalCam = false; getPhysicalCameraIds(cam.id, isLogicalCam); if (mIsHwModule && isLogicalCam) { LOG(INFO) << "Skip a logical device, " << cam.id << " for HW target."; continue; } // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); mActiveCameras.clear(); std::shared_ptr pCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam).isOk()); EXPECT_NE(pCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam); // Verify that this camera self-identifies correctly CameraDesc cameraInfo; ASSERT_TRUE(pCam->getCameraInfo(&cameraInfo).isOk()); EXPECT_EQ(cam.id, cameraInfo.id); std::shared_ptr pCam2; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam2).isOk()); EXPECT_NE(pCam2, nullptr); EXPECT_NE(pCam, pCam2); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam2); auto status = pCam->setMaxFramesInFlight(2); if (mIsHwModule) { // Verify that the old camera rejects calls via HW module. EXPECT_TRUE(!status.isOk() && status.getServiceSpecificError() == static_cast(EvsResult::OWNERSHIP_LOST)); } else { // default implementation supports multiple clients. EXPECT_TRUE(status.isOk()); } // Close the superseded camera ASSERT_TRUE(mEnumerator->closeCamera(pCam).isOk()); mActiveCameras.pop_front(); // Verify that the second camera instance self-identifies correctly ASSERT_TRUE(pCam2->getCameraInfo(&cameraInfo).isOk()); EXPECT_EQ(cam.id, cameraInfo.id); // Close the second camera instance ASSERT_TRUE(mEnumerator->closeCamera(pCam2).isOk()); mActiveCameras.pop_front(); } // Sleep here to ensure the destructor cleanup has time to run so we don't break follow on tests sleep(1); // I hate that this is an arbitrary time to wait. :( b/36122635 } /* * CameraStreamPerformance: * Measure and qualify the stream start up time and streaming frame rate of each reported camera */ TEST_P(EvsAidlTest, CameraStreamPerformance) { LOG(INFO) << "Starting CameraStreamPerformance test"; // Get the camera list loadCameraList(); // Test each reported camera for (auto&& cam : mCameraInfo) { bool isLogicalCam = false; auto devices = getPhysicalCameraIds(cam.id, isLogicalCam); if (mIsHwModule && isLogicalCam) { LOG(INFO) << "Skip a logical device " << cam.id; continue; } // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); std::shared_ptr pCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam).isOk()); EXPECT_NE(pCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam); // Set up a frame receiver object which will fire up its own thread std::shared_ptr frameHandler = ndk::SharedRefBase::make( pCam, cam, nullptr, FrameHandler::eAutoReturn); EXPECT_NE(frameHandler, nullptr); // Start the camera's video stream nsecs_t start = systemTime(SYSTEM_TIME_MONOTONIC); ASSERT_TRUE(frameHandler->startStream()); // Ensure the first frame arrived within the expected time frameHandler->waitForFrameCount(1); nsecs_t firstFrame = systemTime(SYSTEM_TIME_MONOTONIC); nsecs_t timeToFirstFrame = systemTime(SYSTEM_TIME_MONOTONIC) - start; // Extra delays are expected when we attempt to start a video stream on // the logical camera device. The amount of delay is expected the // number of physical camera devices multiplied by // kMaxStreamStartMilliseconds at most. EXPECT_LE(nanoseconds_to_milliseconds(timeToFirstFrame), kMaxStreamStartMilliseconds * devices.size()); printf("%s: Measured time to first frame %0.2f ms\n", cam.id.data(), timeToFirstFrame * kNanoToMilliseconds); LOG(INFO) << cam.id << ": Measured time to first frame " << std::scientific << timeToFirstFrame * kNanoToMilliseconds << " ms."; // Check aspect ratio unsigned width = 0, height = 0; frameHandler->getFrameDimension(&width, &height); EXPECT_GE(width, height); // Wait a bit, then ensure we get at least the required minimum number of frames sleep(5); nsecs_t end = systemTime(SYSTEM_TIME_MONOTONIC); // Even when the camera pointer goes out of scope, the FrameHandler object will // keep the stream alive unless we tell it to shutdown. // Also note that the FrameHandle and the Camera have a mutual circular reference, so // we have to break that cycle in order for either of them to get cleaned up. frameHandler->shutdown(); unsigned framesReceived = 0; frameHandler->getFramesCounters(&framesReceived, nullptr); framesReceived = framesReceived - 1; // Back out the first frame we already waited for nsecs_t runTime = end - firstFrame; float framesPerSecond = framesReceived / (runTime * kNanoToSeconds); printf("Measured camera rate %3.2f fps\n", framesPerSecond); LOG(INFO) << "Measured camera rate " << std::scientific << framesPerSecond << " fps."; EXPECT_GE(framesPerSecond, kMinimumFramesPerSecond); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam).isOk()); mActiveCameras.clear(); } } /* * CameraStreamBuffering: * Ensure the camera implementation behaves properly when the client holds onto buffers for more * than one frame time. The camera must cleanly skip frames until the client is ready again. */ TEST_P(EvsAidlTest, CameraStreamBuffering) { LOG(INFO) << "Starting CameraStreamBuffering test"; // Arbitrary constant (should be > 1 and not too big) static const unsigned int kBuffersToHold = 6; // Get the camera list loadCameraList(); // Test each reported camera for (auto&& cam : mCameraInfo) { bool isLogicalCam = false; getPhysicalCameraIds(cam.id, isLogicalCam); if (mIsHwModule && isLogicalCam) { LOG(INFO) << "Skip a logical device " << cam.id << " for HW target."; continue; } // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); std::shared_ptr pCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam).isOk()); EXPECT_NE(pCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam); // Ask for a very large number of buffers in flight to ensure it errors correctly auto badResult = pCam->setMaxFramesInFlight(std::numeric_limits::max()); EXPECT_TRUE(!badResult.isOk() && badResult.getServiceSpecificError() == static_cast(EvsResult::BUFFER_NOT_AVAILABLE)); // Now ask for exactly two buffers in flight as we'll test behavior in that case ASSERT_TRUE(pCam->setMaxFramesInFlight(kBuffersToHold).isOk()); // Set up a frame receiver object which will fire up its own thread. std::shared_ptr frameHandler = ndk::SharedRefBase::make( pCam, cam, nullptr, FrameHandler::eNoAutoReturn); EXPECT_NE(frameHandler, nullptr); // Start the camera's video stream ASSERT_TRUE(frameHandler->startStream()); // Check that the video stream stalls once we've gotten exactly the number of buffers // we requested since we told the frameHandler not to return them. sleep(1); // 1 second should be enough for at least 5 frames to be delivered worst case unsigned framesReceived = 0; frameHandler->getFramesCounters(&framesReceived, nullptr); ASSERT_EQ(kBuffersToHold, framesReceived) << "Stream didn't stall at expected buffer limit"; // Give back one buffer ASSERT_TRUE(frameHandler->returnHeldBuffer()); // Once we return a buffer, it shouldn't take more than 1/10 second to get a new one // filled since we require 10fps minimum -- but give a 10% allowance just in case. usleep(110 * kMillisecondsToMicroseconds); frameHandler->getFramesCounters(&framesReceived, nullptr); EXPECT_EQ(kBuffersToHold + 1, framesReceived) << "Stream should've resumed"; // Even when the camera pointer goes out of scope, the FrameHandler object will // keep the stream alive unless we tell it to shutdown. // Also note that the FrameHandle and the Camera have a mutual circular reference, so // we have to break that cycle in order for either of them to get cleaned up. frameHandler->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam).isOk()); mActiveCameras.clear(); } } /* * CameraToDisplayRoundTrip: * End to end test of data flowing from the camera to the display. Each delivered frame of camera * imagery is simply copied to the display buffer and presented on screen. This is the one test * which a human could observe to see the operation of the system on the physical display. */ TEST_P(EvsAidlTest, CameraToDisplayRoundTrip) { LOG(INFO) << "Starting CameraToDisplayRoundTrip test"; // Get the camera list loadCameraList(); // Request available display IDs uint8_t targetDisplayId = 0; std::vector displayIds; ASSERT_TRUE(mEnumerator->getDisplayIdList(&displayIds).isOk()); EXPECT_GT(displayIds.size(), 0); targetDisplayId = displayIds[0]; // Test each reported camera for (auto&& cam : mCameraInfo) { // Request exclusive access to the first EVS display std::shared_ptr pDisplay; ASSERT_TRUE(mEnumerator->openDisplay(targetDisplayId, &pDisplay).isOk()); EXPECT_NE(pDisplay, nullptr); LOG(INFO) << "Display " << static_cast(targetDisplayId) << " is in use."; // Get the display descriptor DisplayDesc displayDesc; ASSERT_TRUE(pDisplay->getDisplayInfo(&displayDesc).isOk()); LOG(INFO) << " Resolution: " << displayDesc.width << "x" << displayDesc.height; ASSERT_GT(displayDesc.width, 0); ASSERT_GT(displayDesc.height, 0); bool isLogicalCam = false; getPhysicalCameraIds(cam.id, isLogicalCam); if (mIsHwModule && isLogicalCam) { LOG(INFO) << "Skip a logical device " << cam.id << " for HW target."; ASSERT_TRUE(mEnumerator->closeDisplay(pDisplay).isOk()); continue; } // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); std::shared_ptr pCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam).isOk()); EXPECT_NE(pCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam); // Set up a frame receiver object which will fire up its own thread. std::shared_ptr frameHandler = ndk::SharedRefBase::make( pCam, cam, pDisplay, FrameHandler::eAutoReturn); EXPECT_NE(frameHandler, nullptr); // Activate the display ASSERT_TRUE(pDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME).isOk()); // Start the camera's video stream ASSERT_TRUE(frameHandler->startStream()); // Wait a while to let the data flow static const int kSecondsToWait = 5; const int streamTimeMs = kSecondsToWait * kSecondsToMilliseconds - kMaxStreamStartMilliseconds; const unsigned minimumFramesExpected = streamTimeMs * kMinimumFramesPerSecond / kSecondsToMilliseconds; sleep(kSecondsToWait); unsigned framesReceived = 0; unsigned framesDisplayed = 0; frameHandler->getFramesCounters(&framesReceived, &framesDisplayed); EXPECT_EQ(framesReceived, framesDisplayed); EXPECT_GE(framesDisplayed, minimumFramesExpected); // Turn off the display (yes, before the stream stops -- it should be handled) ASSERT_TRUE(pDisplay->setDisplayState(DisplayState::NOT_VISIBLE).isOk()); // Shut down the streamer frameHandler->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam).isOk()); mActiveCameras.clear(); // Explicitly release the display ASSERT_TRUE(mEnumerator->closeDisplay(pDisplay).isOk()); } } /* * MultiCameraStream: * Verify that each client can start and stop video streams on the same * underlying camera. */ TEST_P(EvsAidlTest, MultiCameraStream) { LOG(INFO) << "Starting MultiCameraStream test"; if (mIsHwModule) { // This test is not for HW module implementation. return; } // Get the camera list loadCameraList(); // Test each reported camera for (auto&& cam : mCameraInfo) { // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); // Create two camera clients. std::shared_ptr pCam0; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam0).isOk()); EXPECT_NE(pCam0, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam0); std::shared_ptr pCam1; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam1).isOk()); EXPECT_NE(pCam1, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam1); // Set up per-client frame receiver objects which will fire up its own thread std::shared_ptr frameHandler0 = ndk::SharedRefBase::make( pCam0, cam, nullptr, FrameHandler::eAutoReturn); std::shared_ptr frameHandler1 = ndk::SharedRefBase::make( pCam1, cam, nullptr, FrameHandler::eAutoReturn); EXPECT_NE(frameHandler0, nullptr); EXPECT_NE(frameHandler1, nullptr); // Start the camera's video stream via client 0 ASSERT_TRUE(frameHandler0->startStream()); ASSERT_TRUE(frameHandler1->startStream()); // Ensure the stream starts frameHandler0->waitForFrameCount(1); frameHandler1->waitForFrameCount(1); nsecs_t firstFrame = systemTime(SYSTEM_TIME_MONOTONIC); // Wait a bit, then ensure both clients get at least the required minimum number of frames sleep(5); nsecs_t end = systemTime(SYSTEM_TIME_MONOTONIC); unsigned framesReceived0 = 0, framesReceived1 = 0; frameHandler0->getFramesCounters(&framesReceived0, nullptr); frameHandler1->getFramesCounters(&framesReceived1, nullptr); framesReceived0 = framesReceived0 - 1; // Back out the first frame we already waited for framesReceived1 = framesReceived1 - 1; // Back out the first frame we already waited for nsecs_t runTime = end - firstFrame; float framesPerSecond0 = framesReceived0 / (runTime * kNanoToSeconds); float framesPerSecond1 = framesReceived1 / (runTime * kNanoToSeconds); LOG(INFO) << "Measured camera rate " << std::scientific << framesPerSecond0 << " fps and " << framesPerSecond1 << " fps"; EXPECT_GE(framesPerSecond0, kMinimumFramesPerSecond); EXPECT_GE(framesPerSecond1, kMinimumFramesPerSecond); // Shutdown one client frameHandler0->shutdown(); // Read frame counters again frameHandler0->getFramesCounters(&framesReceived0, nullptr); frameHandler1->getFramesCounters(&framesReceived1, nullptr); // Wait a bit again sleep(5); unsigned framesReceivedAfterStop0 = 0, framesReceivedAfterStop1 = 0; frameHandler0->getFramesCounters(&framesReceivedAfterStop0, nullptr); frameHandler1->getFramesCounters(&framesReceivedAfterStop1, nullptr); EXPECT_EQ(framesReceived0, framesReceivedAfterStop0); EXPECT_LT(framesReceived1, framesReceivedAfterStop1); // Shutdown another frameHandler1->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam0).isOk()); ASSERT_TRUE(mEnumerator->closeCamera(pCam1).isOk()); mActiveCameras.clear(); // TODO(b/145459970, b/145457727): below sleep() is added to ensure the // destruction of active camera objects; this may be related with two // issues. sleep(1); } } /* * CameraParameter: * Verify that a client can adjust a camera parameter. */ TEST_P(EvsAidlTest, CameraParameter) { LOG(INFO) << "Starting CameraParameter test"; // Get the camera list loadCameraList(); // Test each reported camera for (auto&& cam : mCameraInfo) { bool isLogicalCam = false; getPhysicalCameraIds(cam.id, isLogicalCam); if (isLogicalCam) { // TODO(b/145465724): Support camera parameter programming on // logical devices. LOG(INFO) << "Skip a logical device " << cam.id; continue; } // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); // Create a camera client std::shared_ptr pCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam).isOk()); EXPECT_NE(pCam, nullptr); // Store a camera mActiveCameras.push_back(pCam); // Get the parameter list std::vector cmds; ASSERT_TRUE(pCam->getParameterList(&cmds).isOk()); if (cmds.size() < 1) { continue; } // Set up per-client frame receiver objects which will fire up its own thread std::shared_ptr frameHandler = ndk::SharedRefBase::make( pCam, cam, nullptr, FrameHandler::eAutoReturn); EXPECT_NE(frameHandler, nullptr); // Start the camera's video stream ASSERT_TRUE(frameHandler->startStream()); // Ensure the stream starts frameHandler->waitForFrameCount(1); // Set current client is the primary client ASSERT_TRUE(pCam->setPrimaryClient().isOk()); for (auto& cmd : cmds) { // Get a valid parameter value range ParameterRange range; ASSERT_TRUE(pCam->getIntParameterRange(cmd, &range).isOk()); std::vector values; if (cmd == CameraParam::ABSOLUTE_FOCUS) { // Try to turn off auto-focus ASSERT_TRUE(pCam->setIntParameter(CameraParam::AUTO_FOCUS, 0, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(v, 0); } } // Try to program a parameter with a random value [minVal, maxVal] int32_t val0 = range.min + (std::rand() % (range.max - range.min)); // Rounding down val0 = val0 - (val0 % range.step); values.clear(); ASSERT_TRUE(pCam->setIntParameter(cmd, val0, &values).isOk()); values.clear(); ASSERT_TRUE(pCam->getIntParameter(cmd, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(val0, v) << "Values are not matched."; } } ASSERT_TRUE(pCam->unsetPrimaryClient().isOk()); // Shutdown frameHandler->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam).isOk()); mActiveCameras.clear(); } } /* * CameraPrimaryClientRelease * Verify that non-primary client gets notified when the primary client either * terminates or releases a role. */ TEST_P(EvsAidlTest, CameraPrimaryClientRelease) { LOG(INFO) << "Starting CameraPrimaryClientRelease test"; if (mIsHwModule) { // This test is not for HW module implementation. return; } // Get the camera list loadCameraList(); // Test each reported camera for (auto&& cam : mCameraInfo) { bool isLogicalCam = false; getPhysicalCameraIds(cam.id, isLogicalCam); if (isLogicalCam) { // TODO(b/145465724): Support camera parameter programming on // logical devices. LOG(INFO) << "Skip a logical device " << cam.id; continue; } // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); // Create two camera clients. std::shared_ptr pPrimaryCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pPrimaryCam).isOk()); EXPECT_NE(pPrimaryCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pPrimaryCam); std::shared_ptr pSecondaryCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pSecondaryCam).isOk()); EXPECT_NE(pSecondaryCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pSecondaryCam); // Set up per-client frame receiver objects which will fire up its own thread std::shared_ptr frameHandlerPrimary = ndk::SharedRefBase::make( pPrimaryCam, cam, nullptr, FrameHandler::eAutoReturn); std::shared_ptr frameHandlerSecondary = ndk::SharedRefBase::make(pSecondaryCam, cam, nullptr, FrameHandler::eAutoReturn); EXPECT_NE(frameHandlerPrimary, nullptr); EXPECT_NE(frameHandlerSecondary, nullptr); // Set one client as the primary client ASSERT_TRUE(pPrimaryCam->setPrimaryClient().isOk()); // Try to set another client as the primary client. ASSERT_FALSE(pSecondaryCam->setPrimaryClient().isOk()); // Start the camera's video stream via a primary client client. ASSERT_TRUE(frameHandlerPrimary->startStream()); // Ensure the stream starts frameHandlerPrimary->waitForFrameCount(1); // Start the camera's video stream via another client ASSERT_TRUE(frameHandlerSecondary->startStream()); // Ensure the stream starts frameHandlerSecondary->waitForFrameCount(1); // Non-primary client expects to receive a primary client role relesed // notification. EvsEventDesc aTargetEvent = {}; EvsEventDesc aNotification = {}; bool listening = false; std::mutex eventLock; std::condition_variable eventCond; std::thread listener = std::thread([&aNotification, &frameHandlerSecondary, &listening, &eventCond]() { // Notify that a listening thread is running. listening = true; eventCond.notify_all(); EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::MASTER_RELEASED; if (!frameHandlerSecondary->waitForEvent(aTargetEvent, aNotification, true)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a listening thread starts. std::unique_lock lock(eventLock); auto timer = std::chrono::system_clock::now(); while (!listening) { timer += 1s; eventCond.wait_until(lock, timer); } lock.unlock(); // Release a primary client role. ASSERT_TRUE(pPrimaryCam->unsetPrimaryClient().isOk()); // Join a listening thread. if (listener.joinable()) { listener.join(); } // Verify change notifications. ASSERT_EQ(EvsEventType::MASTER_RELEASED, static_cast(aNotification.aType)); // Non-primary becomes a primary client. ASSERT_TRUE(pSecondaryCam->setPrimaryClient().isOk()); // Previous primary client fails to become a primary client. ASSERT_FALSE(pPrimaryCam->setPrimaryClient().isOk()); listening = false; listener = std::thread([&aNotification, &frameHandlerPrimary, &listening, &eventCond]() { // Notify that a listening thread is running. listening = true; eventCond.notify_all(); EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::MASTER_RELEASED; if (!frameHandlerPrimary->waitForEvent(aTargetEvent, aNotification, true)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a listening thread starts. timer = std::chrono::system_clock::now(); lock.lock(); while (!listening) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); // Closing current primary client. frameHandlerSecondary->shutdown(); // Join a listening thread. if (listener.joinable()) { listener.join(); } // Verify change notifications. ASSERT_EQ(EvsEventType::MASTER_RELEASED, static_cast(aNotification.aType)); // Closing streams. frameHandlerPrimary->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pPrimaryCam).isOk()); ASSERT_TRUE(mEnumerator->closeCamera(pSecondaryCam).isOk()); mActiveCameras.clear(); } } /* * MultiCameraParameter: * Verify that primary and non-primary clients behave as expected when they try to adjust * camera parameters. */ TEST_P(EvsAidlTest, MultiCameraParameter) { LOG(INFO) << "Starting MultiCameraParameter test"; if (mIsHwModule) { // This test is not for HW module implementation. return; } // Get the camera list loadCameraList(); // Test each reported camera for (auto&& cam : mCameraInfo) { bool isLogicalCam = false; getPhysicalCameraIds(cam.id, isLogicalCam); if (isLogicalCam) { // TODO(b/145465724): Support camera parameter programming on // logical devices. LOG(INFO) << "Skip a logical device " << cam.id; continue; } // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); // Create two camera clients. std::shared_ptr pPrimaryCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pPrimaryCam).isOk()); EXPECT_NE(pPrimaryCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pPrimaryCam); std::shared_ptr pSecondaryCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pSecondaryCam).isOk()); EXPECT_NE(pSecondaryCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pSecondaryCam); // Get the parameter list std::vector camPrimaryCmds, camSecondaryCmds; ASSERT_TRUE(pPrimaryCam->getParameterList(&camPrimaryCmds).isOk()); ASSERT_TRUE(pSecondaryCam->getParameterList(&camSecondaryCmds).isOk()); if (camPrimaryCmds.size() < 1 || camSecondaryCmds.size() < 1) { // Skip a camera device if it does not support any parameter. continue; } // Set up per-client frame receiver objects which will fire up its own thread std::shared_ptr frameHandlerPrimary = ndk::SharedRefBase::make( pPrimaryCam, cam, nullptr, FrameHandler::eAutoReturn); std::shared_ptr frameHandlerSecondary = ndk::SharedRefBase::make(pSecondaryCam, cam, nullptr, FrameHandler::eAutoReturn); EXPECT_NE(frameHandlerPrimary, nullptr); EXPECT_NE(frameHandlerSecondary, nullptr); // Set one client as the primary client. ASSERT_TRUE(pPrimaryCam->setPrimaryClient().isOk()); // Try to set another client as the primary client. ASSERT_FALSE(pSecondaryCam->setPrimaryClient().isOk()); // Start the camera's video stream via a primary client client. ASSERT_TRUE(frameHandlerPrimary->startStream()); // Ensure the stream starts frameHandlerPrimary->waitForFrameCount(1); // Start the camera's video stream via another client ASSERT_TRUE(frameHandlerSecondary->startStream()); // Ensure the stream starts frameHandlerSecondary->waitForFrameCount(1); int32_t val0 = 0; std::vector values; EvsEventDesc aNotification0 = {}; EvsEventDesc aNotification1 = {}; for (auto& cmd : camPrimaryCmds) { // Get a valid parameter value range ParameterRange range; ASSERT_TRUE(pPrimaryCam->getIntParameterRange(cmd, &range).isOk()); if (cmd == CameraParam::ABSOLUTE_FOCUS) { // Try to turn off auto-focus values.clear(); ASSERT_TRUE( pPrimaryCam->setIntParameter(CameraParam::AUTO_FOCUS, 0, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(v, 0); } } // Calculate a parameter value to program. val0 = range.min + (std::rand() % (range.max - range.min)); val0 = val0 - (val0 % range.step); // Prepare and start event listeners. bool listening0 = false; bool listening1 = false; std::condition_variable eventCond; std::thread listener0 = std::thread([cmd, val0, &aNotification0, &frameHandlerPrimary, &listening0, &listening1, &eventCond]() { listening0 = true; if (listening1) { eventCond.notify_all(); } EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; aTargetEvent.payload.push_back(static_cast(cmd)); aTargetEvent.payload.push_back(val0); if (!frameHandlerPrimary->waitForEvent(aTargetEvent, aNotification0)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); std::thread listener1 = std::thread([cmd, val0, &aNotification1, &frameHandlerSecondary, &listening0, &listening1, &eventCond]() { listening1 = true; if (listening0) { eventCond.notify_all(); } EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; aTargetEvent.payload.push_back(static_cast(cmd)); aTargetEvent.payload.push_back(val0); if (!frameHandlerSecondary->waitForEvent(aTargetEvent, aNotification1)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a listening thread starts. std::mutex eventLock; std::unique_lock lock(eventLock); auto timer = std::chrono::system_clock::now(); while (!listening0 || !listening1) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); // Try to program a parameter values.clear(); ASSERT_TRUE(pPrimaryCam->setIntParameter(cmd, val0, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(val0, v) << "Values are not matched."; } // Join a listening thread. if (listener0.joinable()) { listener0.join(); } if (listener1.joinable()) { listener1.join(); } // Verify a change notification ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, static_cast(aNotification0.aType)); ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, static_cast(aNotification1.aType)); ASSERT_GE(aNotification0.payload.size(), 2); ASSERT_GE(aNotification1.payload.size(), 2); ASSERT_EQ(cmd, static_cast(aNotification0.payload[0])); ASSERT_EQ(cmd, static_cast(aNotification1.payload[0])); for (auto&& v : values) { ASSERT_EQ(v, aNotification0.payload[1]); ASSERT_EQ(v, aNotification1.payload[1]); } // Clients expects to receive a parameter change notification // whenever a primary client client adjusts it. values.clear(); ASSERT_TRUE(pPrimaryCam->getIntParameter(cmd, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(val0, v) << "Values are not matched."; } } // Try to adjust a parameter via non-primary client values.clear(); ASSERT_FALSE(pSecondaryCam->setIntParameter(camSecondaryCmds[0], val0, &values).isOk()); // Non-primary client attempts to be a primary client ASSERT_FALSE(pSecondaryCam->setPrimaryClient().isOk()); // Primary client retires from a primary client role bool listening = false; std::condition_variable eventCond; std::thread listener = std::thread([&aNotification0, &frameHandlerSecondary, &listening, &eventCond]() { listening = true; eventCond.notify_all(); EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::MASTER_RELEASED; if (!frameHandlerSecondary->waitForEvent(aTargetEvent, aNotification0, true)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); std::mutex eventLock; auto timer = std::chrono::system_clock::now(); std::unique_lock lock(eventLock); while (!listening) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); ASSERT_TRUE(pPrimaryCam->unsetPrimaryClient().isOk()); if (listener.joinable()) { listener.join(); } ASSERT_EQ(EvsEventType::MASTER_RELEASED, static_cast(aNotification0.aType)); // Try to adjust a parameter after being retired values.clear(); ASSERT_FALSE(pPrimaryCam->setIntParameter(camPrimaryCmds[0], val0, &values).isOk()); // Non-primary client becomes a primary client ASSERT_TRUE(pSecondaryCam->setPrimaryClient().isOk()); // Try to adjust a parameter via new primary client for (auto& cmd : camSecondaryCmds) { // Get a valid parameter value range ParameterRange range; ASSERT_TRUE(pSecondaryCam->getIntParameterRange(cmd, &range).isOk()); values.clear(); if (cmd == CameraParam::ABSOLUTE_FOCUS) { // Try to turn off auto-focus values.clear(); ASSERT_TRUE( pSecondaryCam->setIntParameter(CameraParam::AUTO_FOCUS, 0, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(v, 0); } } // Calculate a parameter value to program. This is being rounding down. val0 = range.min + (std::rand() % (range.max - range.min)); val0 = val0 - (val0 % range.step); // Prepare and start event listeners. bool listening0 = false; bool listening1 = false; std::condition_variable eventCond; std::thread listener0 = std::thread([&]() { listening0 = true; if (listening1) { eventCond.notify_all(); } EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; aTargetEvent.payload.push_back(static_cast(cmd)); aTargetEvent.payload.push_back(val0); if (!frameHandlerPrimary->waitForEvent(aTargetEvent, aNotification0)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); std::thread listener1 = std::thread([&]() { listening1 = true; if (listening0) { eventCond.notify_all(); } EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; aTargetEvent.payload.push_back(static_cast(cmd)); aTargetEvent.payload.push_back(val0); if (!frameHandlerSecondary->waitForEvent(aTargetEvent, aNotification1)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a listening thread starts. std::mutex eventLock; std::unique_lock lock(eventLock); auto timer = std::chrono::system_clock::now(); while (!listening0 || !listening1) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); // Try to program a parameter values.clear(); ASSERT_TRUE(pSecondaryCam->setIntParameter(cmd, val0, &values).isOk()); // Clients expects to receive a parameter change notification // whenever a primary client client adjusts it. values.clear(); ASSERT_TRUE(pSecondaryCam->getIntParameter(cmd, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(val0, v) << "Values are not matched."; } // Join a listening thread. if (listener0.joinable()) { listener0.join(); } if (listener1.joinable()) { listener1.join(); } // Verify a change notification ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, static_cast(aNotification0.aType)); ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, static_cast(aNotification1.aType)); ASSERT_GE(aNotification0.payload.size(), 2); ASSERT_GE(aNotification1.payload.size(), 2); ASSERT_EQ(cmd, static_cast(aNotification0.payload[0])); ASSERT_EQ(cmd, static_cast(aNotification1.payload[0])); for (auto&& v : values) { ASSERT_EQ(v, aNotification0.payload[1]); ASSERT_EQ(v, aNotification1.payload[1]); } } // New primary client retires from the role ASSERT_TRUE(pSecondaryCam->unsetPrimaryClient().isOk()); // Shutdown frameHandlerPrimary->shutdown(); frameHandlerSecondary->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pPrimaryCam).isOk()); ASSERT_TRUE(mEnumerator->closeCamera(pSecondaryCam).isOk()); mActiveCameras.clear(); } } /* * HighPriorityCameraClient: * EVS client, which owns the display, is priortized and therefore can take over * a primary client role from other EVS clients without the display. */ TEST_P(EvsAidlTest, HighPriorityCameraClient) { LOG(INFO) << "Starting HighPriorityCameraClient test"; if (mIsHwModule) { // This test is not for HW module implementation. return; } // Get the camera list loadCameraList(); // Test each reported camera for (auto&& cam : mCameraInfo) { // Request available display IDs uint8_t targetDisplayId = 0; std::vector displayIds; ASSERT_TRUE(mEnumerator->getDisplayIdList(&displayIds).isOk()); EXPECT_GT(displayIds.size(), 0); targetDisplayId = displayIds[0]; // Request exclusive access to the EVS display std::shared_ptr pDisplay; ASSERT_TRUE(mEnumerator->openDisplay(targetDisplayId, &pDisplay).isOk()); EXPECT_NE(pDisplay, nullptr); // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); // Create two clients std::shared_ptr pCam0; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam0).isOk()); EXPECT_NE(pCam0, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam0); std::shared_ptr pCam1; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam1).isOk()); EXPECT_NE(pCam1, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam1); // Get the parameter list; this test will use the first command in both // lists. std::vector cam0Cmds, cam1Cmds; ASSERT_TRUE(pCam0->getParameterList(&cam0Cmds).isOk()); ASSERT_TRUE(pCam1->getParameterList(&cam1Cmds).isOk()); if (cam0Cmds.size() < 1 || cam1Cmds.size() < 1) { // Cannot execute this test. ASSERT_TRUE(mEnumerator->closeDisplay(pDisplay).isOk()); continue; } // Set up a frame receiver object which will fire up its own thread. std::shared_ptr frameHandler0 = ndk::SharedRefBase::make( pCam0, cam, nullptr, FrameHandler::eAutoReturn); std::shared_ptr frameHandler1 = ndk::SharedRefBase::make( pCam1, cam, nullptr, FrameHandler::eAutoReturn); EXPECT_NE(frameHandler0, nullptr); EXPECT_NE(frameHandler1, nullptr); // Activate the display ASSERT_TRUE(pDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME).isOk()); // Start the camera's video stream ASSERT_TRUE(frameHandler0->startStream()); ASSERT_TRUE(frameHandler1->startStream()); // Ensure the stream starts frameHandler0->waitForFrameCount(1); frameHandler1->waitForFrameCount(1); // Client 1 becomes a primary client and programs a parameter. // Get a valid parameter value range ParameterRange range; ASSERT_TRUE(pCam1->getIntParameterRange(cam1Cmds[0], &range).isOk()); // Client1 becomes a primary client ASSERT_TRUE(pCam1->setPrimaryClient().isOk()); std::vector values; EvsEventDesc aTargetEvent = {}; EvsEventDesc aNotification = {}; bool listening = false; std::mutex eventLock; std::condition_variable eventCond; if (cam1Cmds[0] == CameraParam::ABSOLUTE_FOCUS) { std::thread listener = std::thread([&frameHandler0, &aNotification, &listening, &eventCond] { listening = true; eventCond.notify_all(); EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; aTargetEvent.payload.push_back( static_cast(CameraParam::AUTO_FOCUS)); aTargetEvent.payload.push_back(0); if (!frameHandler0->waitForEvent(aTargetEvent, aNotification)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a lister starts. std::unique_lock lock(eventLock); auto timer = std::chrono::system_clock::now(); while (!listening) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); // Try to turn off auto-focus ASSERT_TRUE(pCam1->setIntParameter(CameraParam::AUTO_FOCUS, 0, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(v, 0); } // Join a listener if (listener.joinable()) { listener.join(); } // Make sure AUTO_FOCUS is off. ASSERT_EQ(static_cast(aNotification.aType), EvsEventType::PARAMETER_CHANGED); } // Try to program a parameter with a random value [minVal, maxVal] after // rounding it down. int32_t val0 = range.min + (std::rand() % (range.max - range.min)); val0 = val0 - (val0 % range.step); std::thread listener = std::thread( [&frameHandler1, &aNotification, &listening, &eventCond, &cam1Cmds, val0] { listening = true; eventCond.notify_all(); EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; aTargetEvent.payload.push_back(static_cast(cam1Cmds[0])); aTargetEvent.payload.push_back(val0); if (!frameHandler1->waitForEvent(aTargetEvent, aNotification)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a lister starts. listening = false; std::unique_lock lock(eventLock); auto timer = std::chrono::system_clock::now(); while (!listening) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); values.clear(); ASSERT_TRUE(pCam1->setIntParameter(cam1Cmds[0], val0, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(val0, v); } // Join a listener if (listener.joinable()) { listener.join(); } // Verify a change notification ASSERT_EQ(static_cast(aNotification.aType), EvsEventType::PARAMETER_CHANGED); ASSERT_GE(aNotification.payload.size(), 2); ASSERT_EQ(static_cast(aNotification.payload[0]), cam1Cmds[0]); for (auto&& v : values) { ASSERT_EQ(v, aNotification.payload[1]); } listener = std::thread([&frameHandler1, &aNotification, &listening, &eventCond] { listening = true; eventCond.notify_all(); EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::MASTER_RELEASED; if (!frameHandler1->waitForEvent(aTargetEvent, aNotification, true)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a lister starts. listening = false; lock.lock(); timer = std::chrono::system_clock::now(); while (!listening) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); // Client 0 steals a primary client role ASSERT_TRUE(pCam0->forcePrimaryClient(pDisplay).isOk()); // Join a listener if (listener.joinable()) { listener.join(); } ASSERT_EQ(static_cast(aNotification.aType), EvsEventType::MASTER_RELEASED); // Client 0 programs a parameter val0 = range.min + (std::rand() % (range.max - range.min)); // Rounding down val0 = val0 - (val0 % range.step); if (cam0Cmds[0] == CameraParam::ABSOLUTE_FOCUS) { std::thread listener = std::thread([&frameHandler1, &aNotification, &listening, &eventCond] { listening = true; eventCond.notify_all(); EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; aTargetEvent.payload.push_back( static_cast(CameraParam::AUTO_FOCUS)); aTargetEvent.payload.push_back(0); if (!frameHandler1->waitForEvent(aTargetEvent, aNotification)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a lister starts. std::unique_lock lock(eventLock); auto timer = std::chrono::system_clock::now(); while (!listening) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); // Try to turn off auto-focus values.clear(); ASSERT_TRUE(pCam0->setIntParameter(CameraParam::AUTO_FOCUS, 0, &values).isOk()); for (auto&& v : values) { EXPECT_EQ(v, 0); } // Join a listener if (listener.joinable()) { listener.join(); } // Make sure AUTO_FOCUS is off. ASSERT_EQ(static_cast(aNotification.aType), EvsEventType::PARAMETER_CHANGED); } listener = std::thread( [&frameHandler0, &aNotification, &listening, &eventCond, &cam0Cmds, val0] { listening = true; eventCond.notify_all(); EvsEventDesc aTargetEvent; aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; aTargetEvent.payload.push_back(static_cast(cam0Cmds[0])); aTargetEvent.payload.push_back(val0); if (!frameHandler0->waitForEvent(aTargetEvent, aNotification)) { LOG(WARNING) << "A timer is expired before a target event is fired."; } }); // Wait until a lister starts. listening = false; timer = std::chrono::system_clock::now(); lock.lock(); while (!listening) { eventCond.wait_until(lock, timer + 1s); } lock.unlock(); values.clear(); ASSERT_TRUE(pCam0->setIntParameter(cam0Cmds[0], val0, &values).isOk()); // Join a listener if (listener.joinable()) { listener.join(); } // Verify a change notification ASSERT_EQ(static_cast(aNotification.aType), EvsEventType::PARAMETER_CHANGED); ASSERT_GE(aNotification.payload.size(), 2); ASSERT_EQ(static_cast(aNotification.payload[0]), cam0Cmds[0]); for (auto&& v : values) { ASSERT_EQ(v, aNotification.payload[1]); } // Turn off the display (yes, before the stream stops -- it should be handled) ASSERT_TRUE(pDisplay->setDisplayState(DisplayState::NOT_VISIBLE).isOk()); // Shut down the streamer frameHandler0->shutdown(); frameHandler1->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam0).isOk()); ASSERT_TRUE(mEnumerator->closeCamera(pCam1).isOk()); mActiveCameras.clear(); // Explicitly release the display ASSERT_TRUE(mEnumerator->closeDisplay(pDisplay).isOk()); } } /* * CameraUseStreamConfigToDisplay: * End to end test of data flowing from the camera to the display. Similar to * CameraToDisplayRoundTrip test case but this case retrieves available stream * configurations from EVS and uses one of them to start a video stream. */ TEST_P(EvsAidlTest, CameraUseStreamConfigToDisplay) { LOG(INFO) << "Starting CameraUseStreamConfigToDisplay test"; // Get the camera list loadCameraList(); // Request available display IDs uint8_t targetDisplayId = 0; std::vector displayIds; ASSERT_TRUE(mEnumerator->getDisplayIdList(&displayIds).isOk()); EXPECT_GT(displayIds.size(), 0); targetDisplayId = displayIds[0]; // Test each reported camera for (auto&& cam : mCameraInfo) { // Request exclusive access to the EVS display std::shared_ptr pDisplay; ASSERT_TRUE(mEnumerator->openDisplay(targetDisplayId, &pDisplay).isOk()); EXPECT_NE(pDisplay, nullptr); // choose a configuration that has a frame rate faster than minReqFps. Stream targetCfg = {}; const int32_t minReqFps = 15; int32_t maxArea = 0; camera_metadata_entry_t streamCfgs; bool foundCfg = false; if (!find_camera_metadata_entry(reinterpret_cast(cam.metadata.data()), ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &streamCfgs)) { // Stream configurations are found in metadata RawStreamConfig* ptr = reinterpret_cast(streamCfgs.data.i32); for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) { if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) { if (ptr->width * ptr->height > maxArea && ptr->framerate >= minReqFps) { targetCfg.width = ptr->width; targetCfg.height = ptr->height; targetCfg.format = static_cast(ptr->format); maxArea = ptr->width * ptr->height; foundCfg = true; } } ++ptr; } } if (!foundCfg) { // Current EVS camera does not provide stream configurations in the // metadata. continue; } std::shared_ptr pCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam).isOk()); EXPECT_NE(pCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam); // Set up a frame receiver object which will fire up its own thread. std::shared_ptr frameHandler = ndk::SharedRefBase::make( pCam, cam, pDisplay, FrameHandler::eAutoReturn); EXPECT_NE(frameHandler, nullptr); // Activate the display ASSERT_TRUE(pDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME).isOk()); // Start the camera's video stream ASSERT_TRUE(frameHandler->startStream()); // Wait a while to let the data flow static const int kSecondsToWait = 5; const int streamTimeMs = kSecondsToWait * kSecondsToMilliseconds - kMaxStreamStartMilliseconds; const unsigned minimumFramesExpected = streamTimeMs * kMinimumFramesPerSecond / kSecondsToMilliseconds; sleep(kSecondsToWait); unsigned framesReceived = 0; unsigned framesDisplayed = 0; frameHandler->getFramesCounters(&framesReceived, &framesDisplayed); EXPECT_EQ(framesReceived, framesDisplayed); EXPECT_GE(framesDisplayed, minimumFramesExpected); // Turn off the display (yes, before the stream stops -- it should be handled) ASSERT_TRUE(pDisplay->setDisplayState(DisplayState::NOT_VISIBLE).isOk()); // Shut down the streamer frameHandler->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam).isOk()); mActiveCameras.clear(); // Explicitly release the display ASSERT_TRUE(mEnumerator->closeDisplay(pDisplay).isOk()); } } /* * MultiCameraStreamUseConfig: * Verify that each client can start and stop video streams on the same * underlying camera with same configuration. */ TEST_P(EvsAidlTest, MultiCameraStreamUseConfig) { LOG(INFO) << "Starting MultiCameraStream test"; if (mIsHwModule) { // This test is not for HW module implementation. return; } // Get the camera list loadCameraList(); // Test each reported camera for (auto&& cam : mCameraInfo) { // choose a configuration that has a frame rate faster than minReqFps. Stream targetCfg = {}; const int32_t minReqFps = 15; int32_t maxArea = 0; camera_metadata_entry_t streamCfgs; bool foundCfg = false; if (!find_camera_metadata_entry(reinterpret_cast(cam.metadata.data()), ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &streamCfgs)) { // Stream configurations are found in metadata RawStreamConfig* ptr = reinterpret_cast(streamCfgs.data.i32); for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) { if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) { if (ptr->width * ptr->height > maxArea && ptr->framerate >= minReqFps) { targetCfg.width = ptr->width; targetCfg.height = ptr->height; targetCfg.format = static_cast(ptr->format); maxArea = ptr->width * ptr->height; foundCfg = true; } } ++ptr; } } if (!foundCfg) { LOG(INFO) << "Device " << cam.id << " does not provide a list of supported stream configurations, skipped"; continue; } // Create the first camera client with a selected stream configuration. std::shared_ptr pCam0; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam0).isOk()); EXPECT_NE(pCam0, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam0); // Try to create the second camera client with different stream // configuration. int32_t id = targetCfg.id; targetCfg.id += 1; // EVS manager sees only the stream id. std::shared_ptr pCam1; ASSERT_FALSE(mEnumerator->openCamera(cam.id, targetCfg, &pCam1).isOk()); // Try again with same stream configuration. targetCfg.id = id; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam1).isOk()); EXPECT_NE(pCam1, nullptr); // Set up per-client frame receiver objects which will fire up its own thread std::shared_ptr frameHandler0 = ndk::SharedRefBase::make( pCam0, cam, nullptr, FrameHandler::eAutoReturn); std::shared_ptr frameHandler1 = ndk::SharedRefBase::make( pCam1, cam, nullptr, FrameHandler::eAutoReturn); EXPECT_NE(frameHandler0, nullptr); EXPECT_NE(frameHandler1, nullptr); // Start the camera's video stream via client 0 ASSERT_TRUE(frameHandler0->startStream()); ASSERT_TRUE(frameHandler1->startStream()); // Ensure the stream starts frameHandler0->waitForFrameCount(1); frameHandler1->waitForFrameCount(1); nsecs_t firstFrame = systemTime(SYSTEM_TIME_MONOTONIC); // Wait a bit, then ensure both clients get at least the required minimum number of frames sleep(5); nsecs_t end = systemTime(SYSTEM_TIME_MONOTONIC); unsigned framesReceived0 = 0, framesReceived1 = 0; frameHandler0->getFramesCounters(&framesReceived0, nullptr); frameHandler1->getFramesCounters(&framesReceived1, nullptr); framesReceived0 = framesReceived0 - 1; // Back out the first frame we already waited for framesReceived1 = framesReceived1 - 1; // Back out the first frame we already waited for nsecs_t runTime = end - firstFrame; float framesPerSecond0 = framesReceived0 / (runTime * kNanoToSeconds); float framesPerSecond1 = framesReceived1 / (runTime * kNanoToSeconds); LOG(INFO) << "Measured camera rate " << std::scientific << framesPerSecond0 << " fps and " << framesPerSecond1 << " fps"; EXPECT_GE(framesPerSecond0, kMinimumFramesPerSecond); EXPECT_GE(framesPerSecond1, kMinimumFramesPerSecond); // Shutdown one client frameHandler0->shutdown(); // Read frame counters again frameHandler0->getFramesCounters(&framesReceived0, nullptr); frameHandler1->getFramesCounters(&framesReceived1, nullptr); // Wait a bit again sleep(5); unsigned framesReceivedAfterStop0 = 0, framesReceivedAfterStop1 = 0; frameHandler0->getFramesCounters(&framesReceivedAfterStop0, nullptr); frameHandler1->getFramesCounters(&framesReceivedAfterStop1, nullptr); EXPECT_EQ(framesReceived0, framesReceivedAfterStop0); EXPECT_LT(framesReceived1, framesReceivedAfterStop1); // Shutdown another frameHandler1->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam0).isOk()); ASSERT_TRUE(mEnumerator->closeCamera(pCam1).isOk()); mActiveCameras.clear(); } } /* * LogicalCameraMetadata: * Opens logical camera reported by the enumerator and validate its metadata by * checking its capability and locating supporting physical camera device * identifiers. */ TEST_P(EvsAidlTest, LogicalCameraMetadata) { LOG(INFO) << "Starting LogicalCameraMetadata test"; // Get the camera list loadCameraList(); // Open and close each camera twice for (auto&& cam : mCameraInfo) { bool isLogicalCam = false; auto devices = getPhysicalCameraIds(cam.id, isLogicalCam); if (isLogicalCam) { ASSERT_GE(devices.size(), 1) << "Logical camera device must have at least one physical " "camera device ID in its metadata."; } } } /* * CameraStreamExternalBuffering: * This is same with CameraStreamBuffering except frame buffers are allocated by * the test client and then imported by EVS framework. */ TEST_P(EvsAidlTest, CameraStreamExternalBuffering) { LOG(INFO) << "Starting CameraStreamExternalBuffering test"; // Arbitrary constant (should be > 1 and not too big) static const unsigned int kBuffersToHold = 3; // Get the camera list loadCameraList(); // Acquire the graphics buffer allocator android::GraphicBufferAllocator& alloc(android::GraphicBufferAllocator::get()); const auto usage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_OFTEN; // Test each reported camera for (auto&& cam : mCameraInfo) { // Read a target resolution from the metadata Stream targetCfg = getFirstStreamConfiguration( reinterpret_cast(cam.metadata.data())); ASSERT_GT(targetCfg.width, 0); ASSERT_GT(targetCfg.height, 0); // Allocate buffers to use std::vector buffers; buffers.resize(kBuffersToHold); for (auto i = 0; i < kBuffersToHold; ++i) { unsigned pixelsPerLine; buffer_handle_t memHandle = nullptr; android::status_t result = alloc.allocate(targetCfg.width, targetCfg.height, static_cast(targetCfg.format), /* layerCount = */ 1, usage, &memHandle, &pixelsPerLine, /* graphicBufferId = */ 0, /* requestorName = */ "CameraStreamExternalBufferingTest"); if (result != android::NO_ERROR) { LOG(ERROR) << __FUNCTION__ << " failed to allocate memory."; // Release previous allocated buffers for (auto j = 0; j < i; j++) { alloc.free(::android::dupFromAidl(buffers[i].buffer.handle)); } return; } else { BufferDesc buf; HardwareBufferDescription* pDesc = reinterpret_cast(&buf.buffer.description); pDesc->width = targetCfg.width; pDesc->height = targetCfg.height; pDesc->layers = 1; pDesc->format = targetCfg.format; pDesc->usage = static_cast(usage); pDesc->stride = pixelsPerLine; buf.buffer.handle = ::android::dupToAidl(memHandle); buf.bufferId = i; // Unique number to identify this buffer buffers[i] = std::move(buf); } } bool isLogicalCam = false; getPhysicalCameraIds(cam.id, isLogicalCam); std::shared_ptr pCam; ASSERT_TRUE(mEnumerator->openCamera(cam.id, targetCfg, &pCam).isOk()); EXPECT_NE(pCam, nullptr); // Store a camera handle for a clean-up mActiveCameras.push_back(pCam); // Request to import buffers int delta = 0; auto status = pCam->importExternalBuffers(buffers, &delta); if (isLogicalCam) { ASSERT_FALSE(status.isOk()); continue; } ASSERT_TRUE(status.isOk()); EXPECT_GE(delta, kBuffersToHold); // Set up a frame receiver object which will fire up its own thread. std::shared_ptr frameHandler = ndk::SharedRefBase::make( pCam, cam, nullptr, FrameHandler::eNoAutoReturn); EXPECT_NE(frameHandler, nullptr); // Start the camera's video stream ASSERT_TRUE(frameHandler->startStream()); // Check that the video stream stalls once we've gotten exactly the number of buffers // we requested since we told the frameHandler not to return them. sleep(1); // 1 second should be enough for at least 5 frames to be delivered worst case unsigned framesReceived = 0; frameHandler->getFramesCounters(&framesReceived, nullptr); ASSERT_LE(kBuffersToHold, framesReceived) << "Stream didn't stall at expected buffer limit"; // Give back one buffer EXPECT_TRUE(frameHandler->returnHeldBuffer()); // Once we return a buffer, it shouldn't take more than 1/10 second to get a new one // filled since we require 10fps minimum -- but give a 10% allowance just in case. unsigned framesReceivedAfter = 0; usleep(110 * kMillisecondsToMicroseconds); frameHandler->getFramesCounters(&framesReceivedAfter, nullptr); EXPECT_EQ(framesReceived + 1, framesReceivedAfter) << "Stream should've resumed"; // Even when the camera pointer goes out of scope, the FrameHandler object will // keep the stream alive unless we tell it to shutdown. // Also note that the FrameHandle and the Camera have a mutual circular reference, so // we have to break that cycle in order for either of them to get cleaned up. frameHandler->shutdown(); // Explicitly release the camera ASSERT_TRUE(mEnumerator->closeCamera(pCam).isOk()); mActiveCameras.clear(); // Release buffers for (auto& b : buffers) { alloc.free(::android::dupFromAidl(b.buffer.handle)); } buffers.resize(0); } } TEST_P(EvsAidlTest, DeviceStatusCallbackRegistration) { std::shared_ptr cb = ndk::SharedRefBase::make(); ndk::ScopedAStatus status = mEnumerator->registerStatusCallback(cb); if (mIsHwModule) { ASSERT_TRUE(status.isOk()); } else { // A callback registration may fail if a HIDL EVS HAL implementation is // running. ASSERT_TRUE(status.isOk() || status.getServiceSpecificError() == static_cast(EvsResult::NOT_SUPPORTED)); } } /* * UltrasonicsArrayOpenClean: * Opens each ultrasonics arrays reported by the enumerator and then explicitly closes it via a * call to closeUltrasonicsArray. Then repeats the test to ensure all ultrasonics arrays * can be reopened. */ TEST_P(EvsAidlTest, UltrasonicsArrayOpenClean) { LOG(INFO) << "Starting UltrasonicsArrayOpenClean test"; // Get the ultrasonics array list loadUltrasonicsArrayList(); // Open and close each ultrasonics array twice for (auto&& ultraInfo : mUltrasonicsArraysInfo) { for (int pass = 0; pass < 2; pass++) { std::shared_ptr pUltrasonicsArray; ASSERT_TRUE( mEnumerator ->openUltrasonicsArray(ultraInfo.ultrasonicsArrayId, &pUltrasonicsArray) .isOk()); EXPECT_NE(pUltrasonicsArray, nullptr); // Verify that this ultrasonics array self-identifies correctly UltrasonicsArrayDesc desc; ASSERT_TRUE(pUltrasonicsArray->getUltrasonicArrayInfo(&desc).isOk()); EXPECT_EQ(ultraInfo.ultrasonicsArrayId, desc.ultrasonicsArrayId); LOG(DEBUG) << "Found ultrasonics array " << ultraInfo.ultrasonicsArrayId; // Explicitly close the ultrasonics array so resources are released right away ASSERT_TRUE(mEnumerator->closeUltrasonicsArray(pUltrasonicsArray).isOk()); } } } // Starts a stream and verifies all data received is valid. TEST_P(EvsAidlTest, UltrasonicsVerifyStreamData) { LOG(INFO) << "Starting UltrasonicsVerifyStreamData"; // Get the ultrasonics array list loadUltrasonicsArrayList(); // For each ultrasonics array. for (auto&& ultraInfo : mUltrasonicsArraysInfo) { LOG(DEBUG) << "Testing ultrasonics array: " << ultraInfo.ultrasonicsArrayId; std::shared_ptr pUltrasonicsArray; ASSERT_TRUE( mEnumerator->openUltrasonicsArray(ultraInfo.ultrasonicsArrayId, &pUltrasonicsArray) .isOk()); EXPECT_NE(pUltrasonicsArray, nullptr); std::shared_ptr frameHandler = ndk::SharedRefBase::make(pUltrasonicsArray); EXPECT_NE(frameHandler, nullptr); // Start stream. ASSERT_TRUE(pUltrasonicsArray->startStream(frameHandler).isOk()); // Wait 5 seconds to receive frames. sleep(5); // Stop stream. ASSERT_TRUE(pUltrasonicsArray->stopStream().isOk()); EXPECT_GT(frameHandler->getReceiveFramesCount(), 0); EXPECT_TRUE(frameHandler->areAllFramesValid()); // Explicitly close the ultrasonics array so resources are released right away ASSERT_TRUE(mEnumerator->closeUltrasonicsArray(pUltrasonicsArray).isOk()); } } // Sets frames in flight before and after start of stream and verfies success. TEST_P(EvsAidlTest, UltrasonicsSetFramesInFlight) { LOG(INFO) << "Starting UltrasonicsSetFramesInFlight"; // Get the ultrasonics array list loadUltrasonicsArrayList(); // For each ultrasonics array. for (auto&& ultraInfo : mUltrasonicsArraysInfo) { LOG(DEBUG) << "Testing ultrasonics array: " << ultraInfo.ultrasonicsArrayId; std::shared_ptr pUltrasonicsArray; ASSERT_TRUE( mEnumerator->openUltrasonicsArray(ultraInfo.ultrasonicsArrayId, &pUltrasonicsArray) .isOk()); EXPECT_NE(pUltrasonicsArray, nullptr); ASSERT_TRUE(pUltrasonicsArray->setMaxFramesInFlight(10).isOk()); std::shared_ptr frameHandler = ndk::SharedRefBase::make(pUltrasonicsArray); EXPECT_NE(frameHandler, nullptr); // Start stream. ASSERT_TRUE(pUltrasonicsArray->startStream(frameHandler).isOk()); ASSERT_TRUE(pUltrasonicsArray->setMaxFramesInFlight(5).isOk()); // Stop stream. ASSERT_TRUE(pUltrasonicsArray->stopStream().isOk()); // Explicitly close the ultrasonics array so resources are released right away ASSERT_TRUE(mEnumerator->closeUltrasonicsArray(pUltrasonicsArray).isOk()); } } /* * DisplayOpen: * Test both clean shut down and "aggressive open" device stealing behavior. */ TEST_P(EvsAidlTest, DisplayOpen) { LOG(INFO) << "Starting DisplayOpen test"; // Request available display IDs. std::vector displayIds; ASSERT_TRUE(mEnumerator->getDisplayIdList(&displayIds).isOk()); EXPECT_GT(displayIds.size(), 0); for (const auto displayId : displayIds) { std::shared_ptr pDisplay; // Request exclusive access to each EVS display, then let it go. ASSERT_TRUE(mEnumerator->openDisplay(displayId, &pDisplay).isOk()); ASSERT_NE(pDisplay, nullptr); { // Ask the display what its name is. DisplayDesc desc; ASSERT_TRUE(pDisplay->getDisplayInfo(&desc).isOk()); LOG(DEBUG) << "Found display " << desc.id; } ASSERT_TRUE(mEnumerator->closeDisplay(pDisplay).isOk()); // Ensure we can reopen the display after it has been closed. ASSERT_TRUE(mEnumerator->openDisplay(displayId, &pDisplay).isOk()); ASSERT_NE(pDisplay, nullptr); // Open the display while its already open -- ownership should be transferred. std::shared_ptr pDisplay2; ASSERT_TRUE(mEnumerator->openDisplay(displayId, &pDisplay2).isOk()); ASSERT_NE(pDisplay2, nullptr); { // Ensure the old display properly reports its assassination. DisplayState badState; EXPECT_TRUE(pDisplay->getDisplayState(&badState).isOk()); EXPECT_EQ(badState, DisplayState::DEAD); } // Close only the newest display instance -- the other should already be a zombie. ASSERT_TRUE(mEnumerator->closeDisplay(pDisplay2).isOk()); // Finally, validate that we can open the display after the provoked failure above. ASSERT_TRUE(mEnumerator->openDisplay(displayId, &pDisplay).isOk()); ASSERT_NE(pDisplay, nullptr); ASSERT_TRUE(mEnumerator->closeDisplay(pDisplay).isOk()); } } /* * DisplayStates: * Validate that display states transition as expected and can be queried from either the display * object itself or the owning enumerator. */ TEST_P(EvsAidlTest, DisplayStates) { using std::literals::chrono_literals::operator""ms; LOG(INFO) << "Starting DisplayStates test"; // Request available display IDs. std::vector displayIds; ASSERT_TRUE(mEnumerator->getDisplayIdList(&displayIds).isOk()); EXPECT_GT(displayIds.size(), 0); for (const auto displayId : displayIds) { // Ensure the display starts in the expected state. { DisplayState state; EXPECT_FALSE(mEnumerator->getDisplayState(&state).isOk()); } for (const auto displayIdToQuery : displayIds) { DisplayState state; EXPECT_FALSE(mEnumerator->getDisplayStateById(displayIdToQuery, &state).isOk()); } // Scope to limit the lifetime of the pDisplay pointer, and thus the IEvsDisplay object. { // Request exclusive access to the EVS display. std::shared_ptr pDisplay; ASSERT_TRUE(mEnumerator->openDisplay(displayId, &pDisplay).isOk()); ASSERT_NE(pDisplay, nullptr); { DisplayState state; EXPECT_TRUE(mEnumerator->getDisplayState(&state).isOk()); EXPECT_EQ(state, DisplayState::NOT_VISIBLE); } for (const auto displayIdToQuery : displayIds) { DisplayState state; bool get_state_ok = mEnumerator->getDisplayStateById(displayIdToQuery, &state).isOk(); if (displayIdToQuery != displayId) { EXPECT_FALSE(get_state_ok); } else if (get_state_ok) { EXPECT_EQ(state, DisplayState::NOT_VISIBLE); } } // Activate the display. EXPECT_TRUE(pDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME).isOk()); { DisplayState state; EXPECT_TRUE(mEnumerator->getDisplayState(&state).isOk()); EXPECT_EQ(state, DisplayState::VISIBLE_ON_NEXT_FRAME); } { DisplayState state; EXPECT_TRUE(pDisplay->getDisplayState(&state).isOk()); EXPECT_EQ(state, DisplayState::VISIBLE_ON_NEXT_FRAME); } for (const auto displayIdToQuery : displayIds) { DisplayState state; bool get_state_ok = mEnumerator->getDisplayStateById(displayIdToQuery, &state).isOk(); if (displayIdToQuery != displayId) { EXPECT_FALSE(get_state_ok); } else if (get_state_ok) { EXPECT_EQ(state, DisplayState::VISIBLE_ON_NEXT_FRAME); } } // Get the output buffer we'd use to display the imagery. BufferDesc tgtBuffer; ASSERT_TRUE(pDisplay->getTargetBuffer(&tgtBuffer).isOk()); // Send the target buffer back for display (we didn't actually fill anything). EXPECT_TRUE(pDisplay->returnTargetBufferForDisplay(tgtBuffer).isOk()); // Sleep for a tenth of a second to ensure the driver has time to get the image // displayed. std::this_thread::sleep_for(100ms); { DisplayState state; EXPECT_TRUE(mEnumerator->getDisplayState(&state).isOk()); EXPECT_EQ(state, DisplayState::VISIBLE); } { DisplayState state; EXPECT_TRUE(pDisplay->getDisplayState(&state).isOk()); EXPECT_EQ(state, DisplayState::VISIBLE); } for (const auto displayIdToQuery : displayIds) { DisplayState state; bool get_state_ok = mEnumerator->getDisplayStateById(displayIdToQuery, &state).isOk(); if (displayIdToQuery != displayId) { EXPECT_FALSE(get_state_ok); } else if (get_state_ok) { EXPECT_EQ(state, DisplayState::VISIBLE); } } // Turn off the display. EXPECT_TRUE(pDisplay->setDisplayState(DisplayState::NOT_VISIBLE).isOk()); std::this_thread::sleep_for(100ms); { DisplayState state; EXPECT_TRUE(mEnumerator->getDisplayState(&state).isOk()); EXPECT_EQ(state, DisplayState::NOT_VISIBLE); } { DisplayState state; EXPECT_TRUE(pDisplay->getDisplayState(&state).isOk()); EXPECT_EQ(state, DisplayState::NOT_VISIBLE); } for (const auto displayIdToQuery : displayIds) { DisplayState state; bool get_state_ok = mEnumerator->getDisplayStateById(displayIdToQuery, &state).isOk(); if (displayIdToQuery != displayId) { EXPECT_FALSE(get_state_ok); } else if (get_state_ok) { EXPECT_EQ(state, DisplayState::NOT_VISIBLE); } } // Close the display. mEnumerator->closeDisplay(pDisplay); } // Now that the display pointer has gone out of scope, causing the IEvsDisplay interface // object to be destroyed, we should be back to the "not open" state. // NOTE: If we want this to pass without the sleep above, we'd have to add the // (now recommended) closeDisplay() call instead of relying on the smarter pointer // going out of scope. I've not done that because I want to verify that the deletion // of the object does actually clean up (eventually). { DisplayState state; EXPECT_FALSE(mEnumerator->getDisplayState(&state).isOk()); } for (const auto displayIdToQuery : displayIds) { DisplayState state; EXPECT_FALSE(mEnumerator->getDisplayStateById(displayIdToQuery, &state).isOk()); } } } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EvsAidlTest); INSTANTIATE_TEST_SUITE_P( PerInstance, EvsAidlTest, testing::ValuesIn(android::getAidlHalInstanceNames(IEvsEnumerator::descriptor)), android::PrintInstanceNameToString); int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ABinderProcess_setThreadPoolMaxThreadCount(1); ABinderProcess_startThreadPool(); return RUN_ALL_TESTS(); }