blob: 477de3147870e9c6075c18a416158dd3d09b03f8 [file] [log] [blame]
/*
* 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 <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/automotive/evs/BnEvsEnumeratorStatusCallback.h>
#include <aidl/android/hardware/automotive/evs/BufferDesc.h>
#include <aidl/android/hardware/automotive/evs/CameraDesc.h>
#include <aidl/android/hardware/automotive/evs/CameraParam.h>
#include <aidl/android/hardware/automotive/evs/DeviceStatus.h>
#include <aidl/android/hardware/automotive/evs/DisplayDesc.h>
#include <aidl/android/hardware/automotive/evs/DisplayState.h>
#include <aidl/android/hardware/automotive/evs/EvsEventDesc.h>
#include <aidl/android/hardware/automotive/evs/EvsEventType.h>
#include <aidl/android/hardware/automotive/evs/EvsResult.h>
#include <aidl/android/hardware/automotive/evs/IEvsCamera.h>
#include <aidl/android/hardware/automotive/evs/IEvsDisplay.h>
#include <aidl/android/hardware/automotive/evs/IEvsEnumerator.h>
#include <aidl/android/hardware/automotive/evs/IEvsEnumeratorStatusCallback.h>
#include <aidl/android/hardware/automotive/evs/IEvsUltrasonicsArray.h>
#include <aidl/android/hardware/automotive/evs/ParameterRange.h>
#include <aidl/android/hardware/automotive/evs/Stream.h>
#include <aidl/android/hardware/automotive/evs/UltrasonicsArrayDesc.h>
#include <aidl/android/hardware/common/NativeHandle.h>
#include <aidl/android/hardware/graphics/common/HardwareBufferDescription.h>
#include <aidl/android/hardware/graphics/common/PixelFormat.h>
#include <aidlcommonsupport/NativeHandle.h>
#include <android-base/logging.h>
#include <android/binder_ibinder.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <android/binder_status.h>
#include <system/camera_metadata.h>
#include <ui/GraphicBuffer.h>
#include <ui/GraphicBufferAllocator.h>
#include <utils/Timers.h>
#include <chrono>
#include <deque>
#include <thread>
#include <unordered_set>
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<std::string> {
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<int32_t>(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<std::string> getPhysicalCameraIds(const std::string& id, bool& flag) {
std::unordered_set<std::string> 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<camera_metadata_t*>(&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<const char*>(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<RawStreamConfig*>(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<PixelFormat>(ptr->format);
break;
}
++ptr;
}
}
return targetCfg;
}
class DeviceStatusCallback : public BnEvsEnumeratorStatusCallback {
ndk::ScopedAStatus deviceStatusChanged(const std::vector<DeviceStatus>&) override {
// This empty implementation returns always ok().
return ndk::ScopedAStatus::ok();
}
};
// Every test needs access to the service
std::shared_ptr<IEvsEnumerator> mEnumerator;
// Empty unless/util loadCameraList() is called
std::vector<CameraDesc> 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<std::shared_ptr<IEvsCamera>> mActiveCameras;
// Empty unless/util loadUltrasonicsArrayList() is called
std::vector<UltrasonicsArrayDesc> mUltrasonicsArraysInfo;
// A list of active ultrasonics array handles that are to be cleaned up
std::deque<std::weak_ptr<IEvsUltrasonicsArray>> 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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
for (int pass = 0; pass < 2; pass++) {
std::shared_ptr<IEvsCamera> 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<uint8_t> values;
auto status = pCam->setExtendedInfo(id, values);
if (isLogicalCam) {
EXPECT_TRUE(!status.isOk() && status.getServiceSpecificError() ==
static_cast<int>(EvsResult::NOT_SUPPORTED));
} else {
EXPECT_TRUE(status.isOk());
}
status = pCam->getExtendedInfo(id, &values);
if (isLogicalCam) {
EXPECT_TRUE(!status.isOk() && status.getServiceSpecificError() ==
static_cast<int>(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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
mActiveCameras.clear();
std::shared_ptr<IEvsCamera> 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<IEvsCamera> 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<int>(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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
std::shared_ptr<IEvsCamera> 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> frameHandler = ndk::SharedRefBase::make<FrameHandler>(
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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
std::shared_ptr<IEvsCamera> 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<int32_t>::max());
EXPECT_TRUE(!badResult.isOk() && badResult.getServiceSpecificError() ==
static_cast<int>(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> frameHandler = ndk::SharedRefBase::make<FrameHandler>(
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<uint8_t> 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<IEvsDisplay> pDisplay;
ASSERT_TRUE(mEnumerator->openDisplay(targetDisplayId, &pDisplay).isOk());
EXPECT_NE(pDisplay, nullptr);
LOG(INFO) << "Display " << static_cast<int>(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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
std::shared_ptr<IEvsCamera> 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> frameHandler = ndk::SharedRefBase::make<FrameHandler>(
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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
// Create two camera clients.
std::shared_ptr<IEvsCamera> 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<IEvsCamera> 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<FrameHandler> frameHandler0 = ndk::SharedRefBase::make<FrameHandler>(
pCam0, cam, nullptr, FrameHandler::eAutoReturn);
std::shared_ptr<FrameHandler> frameHandler1 = ndk::SharedRefBase::make<FrameHandler>(
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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
// Create a camera client
std::shared_ptr<IEvsCamera> 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<CameraParam> 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> frameHandler = ndk::SharedRefBase::make<FrameHandler>(
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<int32_t> 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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
// Create two camera clients.
std::shared_ptr<IEvsCamera> 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<IEvsCamera> 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<FrameHandler> frameHandlerPrimary = ndk::SharedRefBase::make<FrameHandler>(
pPrimaryCam, cam, nullptr, FrameHandler::eAutoReturn);
std::shared_ptr<FrameHandler> frameHandlerSecondary =
ndk::SharedRefBase::make<FrameHandler>(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<std::mutex> 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<EvsEventType>(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<EvsEventType>(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<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
// Create two camera clients.
std::shared_ptr<IEvsCamera> 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<IEvsCamera> 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<CameraParam> 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<FrameHandler> frameHandlerPrimary = ndk::SharedRefBase::make<FrameHandler>(
pPrimaryCam, cam, nullptr, FrameHandler::eAutoReturn);
std::shared_ptr<FrameHandler> frameHandlerSecondary =
ndk::SharedRefBase::make<FrameHandler>(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<int32_t> 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<int32_t>(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<int32_t>(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<std::mutex> 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<EvsEventType>(aNotification0.aType));
ASSERT_EQ(EvsEventType::PARAMETER_CHANGED,
static_cast<EvsEventType>(aNotification1.aType));
ASSERT_GE(aNotification0.payload.size(), 2);
ASSERT_GE(aNotification1.payload.size(), 2);
ASSERT_EQ(cmd, static_cast<CameraParam>(aNotification0.payload[0]));
ASSERT_EQ(cmd, static_cast<CameraParam>(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<std::mutex> 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<EvsEventType>(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<int32_t>(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<int32_t>(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<std::mutex> 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<EvsEventType>(aNotification0.aType));
ASSERT_EQ(EvsEventType::PARAMETER_CHANGED,
static_cast<EvsEventType>(aNotification1.aType));
ASSERT_GE(aNotification0.payload.size(), 2);
ASSERT_GE(aNotification1.payload.size(), 2);
ASSERT_EQ(cmd, static_cast<CameraParam>(aNotification0.payload[0]));
ASSERT_EQ(cmd, static_cast<CameraParam>(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) {
bool isLogicalCam = false;
if (getPhysicalCameraIds(cam.id, isLogicalCam); isLogicalCam) {
LOG(INFO) << "Skip a logical device, " << cam.id;
continue;
}
// Request available display IDs
uint8_t targetDisplayId = 0;
std::vector<uint8_t> 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<IEvsDisplay> pDisplay;
ASSERT_TRUE(mEnumerator->openDisplay(targetDisplayId, &pDisplay).isOk());
EXPECT_NE(pDisplay, nullptr);
// Read a target resolution from the metadata
Stream targetCfg = getFirstStreamConfiguration(
reinterpret_cast<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
// Create two clients
std::shared_ptr<IEvsCamera> 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<IEvsCamera> 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<CameraParam> 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<FrameHandler> frameHandler0 = ndk::SharedRefBase::make<FrameHandler>(
pCam0, cam, nullptr, FrameHandler::eAutoReturn);
std::shared_ptr<FrameHandler> frameHandler1 = ndk::SharedRefBase::make<FrameHandler>(
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<int32_t> 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<int32_t>(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<std::mutex> 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<EvsEventType>(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<int32_t>(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<std::mutex> 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<EvsEventType>(aNotification.aType), EvsEventType::PARAMETER_CHANGED);
ASSERT_GE(aNotification.payload.size(), 2);
ASSERT_EQ(static_cast<CameraParam>(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<EvsEventType>(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<int32_t>(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<std::mutex> 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<EvsEventType>(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<int32_t>(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<EvsEventType>(aNotification.aType), EvsEventType::PARAMETER_CHANGED);
ASSERT_GE(aNotification.payload.size(), 2);
ASSERT_EQ(static_cast<CameraParam>(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<uint8_t> 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<IEvsDisplay> 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<camera_metadata_t*>(cam.metadata.data()),
ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
&streamCfgs)) {
// Stream configurations are found in metadata
RawStreamConfig* ptr = reinterpret_cast<RawStreamConfig*>(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<PixelFormat>(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<IEvsCamera> 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> frameHandler = ndk::SharedRefBase::make<FrameHandler>(
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<camera_metadata_t*>(cam.metadata.data()),
ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
&streamCfgs)) {
// Stream configurations are found in metadata
RawStreamConfig* ptr = reinterpret_cast<RawStreamConfig*>(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<PixelFormat>(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<IEvsCamera> 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<IEvsCamera> 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<FrameHandler> frameHandler0 = ndk::SharedRefBase::make<FrameHandler>(
pCam0, cam, nullptr, FrameHandler::eAutoReturn);
std::shared_ptr<FrameHandler> frameHandler1 = ndk::SharedRefBase::make<FrameHandler>(
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) {
bool isLogicalCam = false;
getPhysicalCameraIds(cam.id, isLogicalCam);
if (isLogicalCam) {
LOG(INFO) << "Skip a logical device, " << cam.id;
continue;
}
// Read a target resolution from the metadata
Stream targetCfg = getFirstStreamConfiguration(
reinterpret_cast<camera_metadata_t*>(cam.metadata.data()));
ASSERT_GT(targetCfg.width, 0);
ASSERT_GT(targetCfg.height, 0);
// Allocate buffers to use
std::vector<BufferDesc> 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<android::PixelFormat>(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<HardwareBufferDescription*>(&buf.buffer.description);
pDesc->width = targetCfg.width;
pDesc->height = targetCfg.height;
pDesc->layers = 1;
pDesc->format = targetCfg.format;
pDesc->usage = static_cast<BufferUsage>(usage);
pDesc->stride = pixelsPerLine;
buf.buffer.handle = ::android::dupToAidl(memHandle);
buf.bufferId = i; // Unique number to identify this buffer
buffers[i] = std::move(buf);
}
}
std::shared_ptr<IEvsCamera> 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);
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> frameHandler = ndk::SharedRefBase::make<FrameHandler>(
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<IEvsEnumeratorStatusCallback> cb =
ndk::SharedRefBase::make<DeviceStatusCallback>();
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<int>(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<IEvsUltrasonicsArray> 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<IEvsUltrasonicsArray> pUltrasonicsArray;
ASSERT_TRUE(
mEnumerator->openUltrasonicsArray(ultraInfo.ultrasonicsArrayId, &pUltrasonicsArray)
.isOk());
EXPECT_NE(pUltrasonicsArray, nullptr);
std::shared_ptr<FrameHandlerUltrasonics> frameHandler =
ndk::SharedRefBase::make<FrameHandlerUltrasonics>(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<IEvsUltrasonicsArray> pUltrasonicsArray;
ASSERT_TRUE(
mEnumerator->openUltrasonicsArray(ultraInfo.ultrasonicsArrayId, &pUltrasonicsArray)
.isOk());
EXPECT_NE(pUltrasonicsArray, nullptr);
ASSERT_TRUE(pUltrasonicsArray->setMaxFramesInFlight(10).isOk());
std::shared_ptr<FrameHandlerUltrasonics> frameHandler =
ndk::SharedRefBase::make<FrameHandlerUltrasonics>(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<uint8_t> displayIds;
ASSERT_TRUE(mEnumerator->getDisplayIdList(&displayIds).isOk());
EXPECT_GT(displayIds.size(), 0);
for (const auto displayId : displayIds) {
std::shared_ptr<IEvsDisplay> 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<IEvsDisplay> 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<uint8_t> 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<IEvsDisplay> 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();
}