| /* |
| * Copyright (C) 2019 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 "Enumerator.h" |
| #include "HalDisplay.h" |
| #include "emul/EvsEmulatedCamera.h" |
| |
| #include <regex> |
| |
| #include <android-base/chrono_utils.h> |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/parseint.h> |
| #include <android-base/strings.h> |
| #include <android-base/stringprintf.h> |
| #include <cutils/android_filesystem_config.h> |
| #include <hwbinder/IPCThreadState.h> |
| |
| namespace { |
| |
| const char* kSingleIndent = "\t"; |
| const char* kDumpOptionAll = "all"; |
| const char* kDumpDeviceCamera = "camera"; |
| const char* kDumpDeviceDisplay = "display"; |
| |
| const char* kDumpCameraCommandCurrent = "--current"; |
| const char* kDumpCameraCommandCollected = "--collected"; |
| const char* kDumpCameraCommandCustom = "--custom"; |
| const char* kDumpCameraCommandCustomStart = "start"; |
| const char* kDumpCameraCommandCustomStop = "stop"; |
| |
| const int kDumpCameraMinNumArgs = 4; |
| const int kOptionDumpDeviceTypeIndex = 1; |
| const int kOptionDumpCameraTypeIndex = 2; |
| const int kOptionDumpCameraCommandIndex = 3; |
| const int kOptionDumpCameraArgsStartIndex = 4; |
| |
| const std::regex kEmulatedCameraNamePattern("emulated/[0-9]+", std::regex_constants::icase); |
| |
| // Display ID 255 is reserved for the special purpose. |
| constexpr int kExclusiveMainDisplayId = 255; |
| } |
| |
| namespace android { |
| namespace automotive { |
| namespace evs { |
| namespace V1_1 { |
| namespace implementation { |
| |
| using ::android::base::Error; |
| using ::android::base::EqualsIgnoreCase; |
| using ::android::base::StringAppendF; |
| using ::android::base::StringPrintf; |
| using ::android::base::WriteStringToFd; |
| using CameraDesc_1_0 = ::android::hardware::automotive::evs::V1_0::CameraDesc; |
| using CameraDesc_1_1 = ::android::hardware::automotive::evs::V1_1::CameraDesc; |
| |
| Enumerator::~Enumerator() { |
| if (mClientsMonitor != nullptr) { |
| mClientsMonitor->stopCollection(); |
| } |
| } |
| |
| bool Enumerator::init(const char* hardwareServiceName) { |
| LOG(DEBUG) << "init"; |
| |
| // Connect with the underlying hardware enumerator |
| mHwEnumerator = IEvsEnumerator::getService(hardwareServiceName); |
| bool result = (mHwEnumerator != nullptr); |
| if (result) { |
| // Get an internal display identifier. |
| mHwEnumerator->getDisplayIdList( |
| [this](const auto& displayPorts) { |
| for (auto& port : displayPorts) { |
| mDisplayPorts.push_back(port); |
| } |
| |
| // The first element is the internal display |
| mInternalDisplayPort = mDisplayPorts.front(); |
| if (mDisplayPorts.size() < 1) { |
| LOG(WARNING) << "No display is available to EVS service."; |
| } |
| } |
| ); |
| } |
| |
| auto it = std::find(mDisplayPorts.begin(), mDisplayPorts.end(), kExclusiveMainDisplayId); |
| if (it != mDisplayPorts.end()) { |
| LOG(WARNING) << kExclusiveMainDisplayId << " is reserved for the special purpose " |
| << "so will not be available for EVS service."; |
| mDisplayPorts.erase(it); |
| } |
| mDisplayOwnedExclusively = false; |
| |
| // Starts the statistics collection |
| mMonitorEnabled = false; |
| mClientsMonitor = new StatsCollector(); |
| if (mClientsMonitor != nullptr) { |
| auto result = mClientsMonitor->startCollection(); |
| if (!result.ok()) { |
| LOG(ERROR) << "Failed to start the usage monitor: " |
| << result.error(); |
| } else { |
| mMonitorEnabled = true; |
| } |
| } |
| |
| return result; |
| } |
| |
| |
| bool Enumerator::checkPermission() { |
| hardware::IPCThreadState *ipc = hardware::IPCThreadState::self(); |
| const auto userId = ipc->getCallingUid() / AID_USER_OFFSET; |
| const auto appId = ipc->getCallingUid() % AID_USER_OFFSET; |
| #ifdef EVS_DEBUG |
| if (AID_AUTOMOTIVE_EVS != appId && AID_ROOT != appId && AID_SYSTEM != appId) { |
| #else |
| if (AID_AUTOMOTIVE_EVS != appId && AID_SYSTEM != appId) { |
| #endif |
| LOG(ERROR) << "EVS access denied? " |
| << "pid = " << ipc->getCallingPid() |
| << ", userId = " << userId |
| << ", appId = " << appId; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| bool Enumerator::isLogicalCamera(const camera_metadata_t *metadata) { |
| bool found = false; |
| |
| if (metadata == nullptr) { |
| LOG(ERROR) << "Metadata is null"; |
| return found; |
| } |
| |
| camera_metadata_ro_entry_t entry; |
| int rc = find_camera_metadata_ro_entry(metadata, |
| ANDROID_REQUEST_AVAILABLE_CAPABILITIES, |
| &entry); |
| if (0 != rc) { |
| // No capabilities are found in metadata. |
| LOG(DEBUG) << __FUNCTION__ << " does not find a target entry"; |
| return found; |
| } |
| |
| for (size_t i = 0; i < entry.count; ++i) { |
| uint8_t capability = entry.data.u8[i]; |
| if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) { |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) { |
| LOG(DEBUG) << __FUNCTION__ << " does not find a logical multi camera cap"; |
| } |
| return found; |
| } |
| |
| |
| std::unordered_set<std::string> Enumerator::getPhysicalCameraIds(const std::string& id) { |
| std::unordered_set<std::string> physicalCameras; |
| if (mCameraDevices.find(id) == mCameraDevices.end()) { |
| LOG(ERROR) << "Queried device " << id << " does not exist!"; |
| return physicalCameras; |
| } |
| |
| const camera_metadata_t *metadata = |
| reinterpret_cast<camera_metadata_t *>(&mCameraDevices[id].metadata[0]); |
| if (!isLogicalCamera(metadata)) { |
| // EVS assumes that the device w/o a valid metadata is a physical |
| // device. |
| LOG(INFO) << id << " is not a logical camera device."; |
| physicalCameras.emplace(id); |
| return physicalCameras; |
| } |
| |
| camera_metadata_ro_entry entry; |
| int rc = find_camera_metadata_ro_entry(metadata, |
| ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS, |
| &entry); |
| if (0 != rc) { |
| LOG(ERROR) << "No physical camera ID is found for a logical camera device " << id; |
| return physicalCameras; |
| } |
| |
| 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.emplace(id); |
| } |
| start = i + 1; |
| } |
| } |
| |
| LOG(INFO) << id << " consists of " |
| << physicalCameras.size() << " physical camera devices."; |
| return physicalCameras; |
| } |
| |
| |
| // Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow. |
| Return<void> Enumerator::getCameraList(getCameraList_cb list_cb) { |
| hardware::hidl_vec<CameraDesc_1_0> cameraList; |
| mHwEnumerator->getCameraList_1_1([&cameraList](auto cameraList_1_1) { |
| cameraList.resize(cameraList_1_1.size()); |
| unsigned i = 0; |
| for (auto&& cam : cameraList_1_1) { |
| cameraList[i++] = cam.v1; |
| } |
| }); |
| |
| list_cb(cameraList); |
| |
| return Void(); |
| } |
| |
| |
| Return<sp<IEvsCamera_1_0>> Enumerator::openCamera(const hidl_string& cameraId) { |
| LOG(DEBUG) << __FUNCTION__; |
| if (!checkPermission()) { |
| return nullptr; |
| } |
| |
| // Is the underlying hardware camera already open? |
| sp<HalCamera> hwCamera; |
| if (mActiveCameras.find(cameraId) != mActiveCameras.end()) { |
| hwCamera = mActiveCameras[cameraId]; |
| } else { |
| // Is the hardware camera available? |
| sp<IEvsCamera_1_1> device; |
| if (std::regex_match(cameraId.c_str(), kEmulatedCameraNamePattern)) { |
| if (mEmulatedCameraDevices.find(cameraId) == mEmulatedCameraDevices.end()) { |
| LOG(ERROR) << cameraId << " is not available"; |
| } else { |
| device = EvsEmulatedCamera::Create(cameraId.c_str(), |
| mEmulatedCameraDevices[cameraId]); |
| } |
| } else { |
| device = IEvsCamera_1_1::castFrom(mHwEnumerator->openCamera(cameraId)) |
| .withDefault(nullptr); |
| } |
| if (device == nullptr) { |
| LOG(ERROR) << "Failed to open hardware camera " << cameraId; |
| } else { |
| // Calculates the usage statistics record identifier |
| auto fn = mCameraDevices.hash_function(); |
| auto recordId = fn(cameraId) & 0xFF; |
| hwCamera = new HalCamera(device, cameraId, recordId); |
| if (hwCamera == nullptr) { |
| LOG(ERROR) << "Failed to allocate camera wrapper object"; |
| mHwEnumerator->closeCamera(device); |
| } |
| } |
| } |
| |
| // Construct a virtual camera wrapper for this hardware camera |
| sp<VirtualCamera> clientCamera; |
| if (hwCamera != nullptr) { |
| clientCamera = hwCamera->makeVirtualCamera(); |
| } |
| |
| // Add the hardware camera to our list, which will keep it alive via ref count |
| if (clientCamera != nullptr) { |
| mActiveCameras.try_emplace(cameraId, hwCamera); |
| } else { |
| LOG(ERROR) << "Requested camera " << cameraId |
| << " not found or not available"; |
| } |
| |
| // Send the virtual camera object back to the client by strong pointer which will keep it alive |
| return clientCamera; |
| } |
| |
| |
| Return<void> Enumerator::closeCamera(const ::android::sp<IEvsCamera_1_0>& clientCamera) { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| if (clientCamera == nullptr) { |
| LOG(ERROR) << "Ignoring call with null camera pointer."; |
| return Void(); |
| } |
| |
| // All our client cameras are actually VirtualCamera objects |
| sp<VirtualCamera> virtualCamera = reinterpret_cast<VirtualCamera *>(clientCamera.get()); |
| |
| // Find the parent camera that backs this virtual camera |
| for (auto&& halCamera : virtualCamera->getHalCameras()) { |
| // Tell the virtual camera's parent to clean it up and drop it |
| // NOTE: The camera objects will only actually destruct when the sp<> ref counts get to |
| // zero, so it is important to break all cyclic references. |
| halCamera->disownVirtualCamera(virtualCamera); |
| |
| // Did we just remove the last client of this camera? |
| if (halCamera->getClientCount() == 0) { |
| // Take this now unused camera out of our list |
| // NOTE: This should drop our last reference to the camera, resulting in its |
| // destruction. |
| mActiveCameras.erase(halCamera->getId()); |
| if (mMonitorEnabled) { |
| mClientsMonitor->unregisterClientToMonitor(halCamera->getId()); |
| } |
| } |
| } |
| |
| // Make sure the virtual camera's stream is stopped |
| virtualCamera->stopVideoStream(); |
| |
| return Void(); |
| } |
| |
| |
| // Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow. |
| Return<sp<IEvsCamera_1_1>> Enumerator::openCamera_1_1(const hidl_string& cameraId, |
| const Stream& streamCfg) { |
| LOG(DEBUG) << __FUNCTION__; |
| if (!checkPermission()) { |
| return nullptr; |
| } |
| |
| // If hwCamera is null, a requested camera device is either a logical camera |
| // device or a hardware camera, which is not being used now. |
| std::unordered_set<std::string> physicalCameras = getPhysicalCameraIds(cameraId); |
| std::vector<sp<HalCamera>> sourceCameras; |
| sp<HalCamera> hwCamera; |
| bool success = true; |
| |
| // 1. Try to open inactive camera devices. |
| for (auto&& id : physicalCameras) { |
| auto it = mActiveCameras.find(id); |
| if (it == mActiveCameras.end()) { |
| sp<IEvsCamera_1_1> device; |
| if (std::regex_match(cameraId.c_str(), kEmulatedCameraNamePattern)) { |
| if (mEmulatedCameraDevices.find(id) == mEmulatedCameraDevices.end()) { |
| LOG(ERROR) << cameraId << " is not available"; |
| } else { |
| device = EvsEmulatedCamera::Create(id.c_str(), |
| mEmulatedCameraDevices[id]); |
| } |
| } else { |
| device = mHwEnumerator->openCamera_1_1(id, streamCfg); |
| } |
| |
| if (device == nullptr) { |
| LOG(ERROR) << "Failed to open hardware camera " << cameraId; |
| success = false; |
| break; |
| } else { |
| // Calculates the usage statistics record identifier |
| auto fn = mCameraDevices.hash_function(); |
| auto recordId = fn(id) & 0xFF; |
| hwCamera = new HalCamera(device, id, recordId, streamCfg); |
| if (hwCamera == nullptr) { |
| LOG(ERROR) << "Failed to allocate camera wrapper object"; |
| mHwEnumerator->closeCamera(device); |
| success = false; |
| break; |
| } |
| } |
| |
| // Add the hardware camera to our list, which will keep it alive via ref count |
| mActiveCameras.try_emplace(id, hwCamera); |
| if (mMonitorEnabled) { |
| mClientsMonitor->registerClientToMonitor(hwCamera); |
| } |
| |
| sourceCameras.push_back(hwCamera); |
| } else { |
| if (it->second->getStreamConfig().id != streamCfg.id) { |
| LOG(WARNING) << "Requested camera is already active in different configuration."; |
| } else { |
| sourceCameras.push_back(it->second); |
| } |
| } |
| } |
| |
| if (!success || sourceCameras.size() < 1) { |
| LOG(ERROR) << "Failed to open any physical camera device"; |
| return nullptr; |
| } |
| |
| // TODO(b/147170360): Implement a logic to handle a failure. |
| // 3. Create a proxy camera object |
| sp<VirtualCamera> clientCamera = new VirtualCamera(sourceCameras); |
| if (clientCamera == nullptr) { |
| // TODO: Any resource needs to be cleaned up explicitly? |
| LOG(ERROR) << "Failed to create a client camera object"; |
| } else { |
| if (physicalCameras.size() > 1) { |
| // VirtualCamera, which represents a logical device, caches its |
| // descriptor. |
| clientCamera->setDescriptor(&mCameraDevices[cameraId]); |
| } |
| |
| // 4. Owns created proxy camera object |
| for (auto&& hwCamera : sourceCameras) { |
| if (!hwCamera->ownVirtualCamera(clientCamera)) { |
| // TODO: Remove a referece to this camera from a virtual camera |
| // object. |
| LOG(ERROR) << hwCamera->getId() |
| << " failed to own a created proxy camera object."; |
| } |
| } |
| } |
| |
| // Send the virtual camera object back to the client by strong pointer which will keep it alive |
| return clientCamera; |
| } |
| |
| |
| Return<void> Enumerator::getCameraList_1_1(getCameraList_1_1_cb list_cb) { |
| LOG(DEBUG) << __FUNCTION__; |
| if (!checkPermission()) { |
| return Void(); |
| } |
| |
| hardware::hidl_vec<CameraDesc_1_1> hidlCameras; |
| mHwEnumerator->getCameraList_1_1( |
| [&hidlCameras](hardware::hidl_vec<CameraDesc_1_1> enumeratedCameras) { |
| hidlCameras.resize(enumeratedCameras.size()); |
| unsigned count = 0; |
| for (auto&& camdesc : enumeratedCameras) { |
| hidlCameras[count++] = camdesc; |
| } |
| } |
| ); |
| |
| // Update the cached device list |
| mCameraDevices.clear(); |
| for (auto&& desc : hidlCameras) { |
| mCameraDevices.insert_or_assign(desc.v1.cameraId, desc); |
| } |
| |
| // Add emulated devices if there is any |
| if (mEmulatedCameraDevices.size() > 0) { |
| int index = hidlCameras.size(); |
| hidlCameras.resize(hidlCameras.size() + mEmulatedCameraDevices.size()); |
| for (auto&& [id, desc] : mEmulatedCameraDevices) { |
| hidlCameras[index++].v1.cameraId = id; |
| } |
| } |
| |
| list_cb(hidlCameras); |
| return Void(); |
| } |
| |
| |
| Return<sp<IEvsDisplay_1_0>> Enumerator::openDisplay() { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| if (!checkPermission()) { |
| return nullptr; |
| } |
| |
| if (mDisplayOwnedExclusively) { |
| LOG(ERROR) << "Display is owned exclusively by another client."; |
| return nullptr; |
| } |
| |
| // We simply keep track of the most recently opened display instance. |
| // In the underlying layers we expect that a new open will cause the previous |
| // object to be destroyed. This avoids any race conditions associated with |
| // create/destroy order and provides a cleaner restart sequence if the previous owner |
| // is non-responsive for some reason. |
| // Request exclusive access to the EVS display |
| sp<IEvsDisplay_1_0> pActiveDisplay = mHwEnumerator->openDisplay(); |
| if (pActiveDisplay == nullptr) { |
| LOG(ERROR) << "EVS Display unavailable"; |
| |
| return nullptr; |
| } |
| |
| // Remember (via weak pointer) who we think the most recently opened display is so that |
| // we can proxy state requests from other callers to it. |
| // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and |
| // wraps the IEvsDisplay object the driver returns. We may want to remove this |
| // additional class when it is fixed properly. |
| sp<IEvsDisplay_1_0> pHalDisplay = new HalDisplay(pActiveDisplay, mInternalDisplayPort); |
| mActiveDisplay = pHalDisplay; |
| |
| return pHalDisplay; |
| } |
| |
| |
| Return<void> Enumerator::closeDisplay(const ::android::sp<IEvsDisplay_1_0>& display) { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| sp<IEvsDisplay_1_0> pActiveDisplay = mActiveDisplay.promote(); |
| |
| // Drop the active display |
| if (display.get() != pActiveDisplay.get()) { |
| LOG(WARNING) << "Ignoring call to closeDisplay with unrecognized display object."; |
| } else { |
| // Pass this request through to the hardware layer |
| sp<HalDisplay> halDisplay = reinterpret_cast<HalDisplay *>(pActiveDisplay.get()); |
| mHwEnumerator->closeDisplay(halDisplay->getHwDisplay()); |
| mActiveDisplay = nullptr; |
| mDisplayOwnedExclusively = false; |
| } |
| |
| return Void(); |
| } |
| |
| |
| Return<EvsDisplayState> Enumerator::getDisplayState() { |
| LOG(DEBUG) << __FUNCTION__; |
| if (!checkPermission()) { |
| return EvsDisplayState::DEAD; |
| } |
| |
| // Do we have a display object we think should be active? |
| sp<IEvsDisplay_1_0> pActiveDisplay = mActiveDisplay.promote(); |
| if (pActiveDisplay != nullptr) { |
| // Pass this request through to the hardware layer |
| return pActiveDisplay->getDisplayState(); |
| } else { |
| // We don't have a live display right now |
| mActiveDisplay = nullptr; |
| return EvsDisplayState::NOT_OPEN; |
| } |
| } |
| |
| |
| Return<sp<IEvsDisplay_1_1>> Enumerator::openDisplay_1_1(uint8_t id) { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| if (!checkPermission()) { |
| return nullptr; |
| } |
| |
| if (mDisplayOwnedExclusively) { |
| LOG(ERROR) << "Display is owned exclusively by another client."; |
| return nullptr; |
| } |
| |
| if (id == kExclusiveMainDisplayId) { |
| // The client requests to open the primary display exclusively. |
| id = mInternalDisplayPort; |
| mDisplayOwnedExclusively = true; |
| } else if (std::find(mDisplayPorts.begin(), mDisplayPorts.end(), id) == mDisplayPorts.end()) { |
| LOG(ERROR) << "No display is available on the port " << static_cast<int32_t>(id); |
| return nullptr; |
| } |
| |
| // We simply keep track of the most recently opened display instance. |
| // In the underlying layers we expect that a new open will cause the previous |
| // object to be destroyed. This avoids any race conditions associated with |
| // create/destroy order and provides a cleaner restart sequence if the previous owner |
| // is non-responsive for some reason. |
| // Request exclusive access to the EVS display |
| sp<IEvsDisplay_1_1> pActiveDisplay = mHwEnumerator->openDisplay_1_1(id); |
| if (pActiveDisplay == nullptr) { |
| LOG(ERROR) << "EVS Display unavailable"; |
| |
| return nullptr; |
| } |
| |
| // Remember (via weak pointer) who we think the most recently opened display is so that |
| // we can proxy state requests from other callers to it. |
| // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and |
| // wraps the IEvsDisplay object the driver returns. We may want to remove this |
| // additional class when it is fixed properly. |
| sp<IEvsDisplay_1_1> pHalDisplay = new HalDisplay(pActiveDisplay, id); |
| mActiveDisplay = pHalDisplay; |
| |
| return pHalDisplay; |
| } |
| |
| |
| Return<void> Enumerator::getDisplayIdList(getDisplayIdList_cb _list_cb) { |
| return mHwEnumerator->getDisplayIdList(_list_cb); |
| } |
| |
| |
| // TODO(b/149874793): Add implementation for EVS Manager and Sample driver |
| Return<void> Enumerator::getUltrasonicsArrayList(getUltrasonicsArrayList_cb _hidl_cb) { |
| hardware::hidl_vec<UltrasonicsArrayDesc> ultrasonicsArrayDesc; |
| _hidl_cb(ultrasonicsArrayDesc); |
| return Void(); |
| } |
| |
| |
| // TODO(b/149874793): Add implementation for EVS Manager and Sample driver |
| Return<sp<IEvsUltrasonicsArray>> Enumerator::openUltrasonicsArray( |
| const hidl_string& ultrasonicsArrayId) { |
| (void)ultrasonicsArrayId; |
| sp<IEvsUltrasonicsArray> pEvsUltrasonicsArray; |
| return pEvsUltrasonicsArray; |
| } |
| |
| |
| // TODO(b/149874793): Add implementation for EVS Manager and Sample driver |
| Return<void> Enumerator::closeUltrasonicsArray( |
| const ::android::sp<IEvsUltrasonicsArray>& evsUltrasonicsArray) { |
| (void)evsUltrasonicsArray; |
| return Void(); |
| } |
| |
| |
| Return<void> Enumerator::debug(const hidl_handle& fd, |
| const hidl_vec<hidl_string>& options) { |
| if (fd.getNativeHandle() != nullptr && fd->numFds > 0) { |
| cmdDump(fd->data[0], options); |
| } else { |
| LOG(ERROR) << "Given file descriptor is not valid."; |
| } |
| |
| return {}; |
| } |
| |
| |
| void Enumerator::cmdDump(int fd, const hidl_vec<hidl_string>& options) { |
| if (options.size() == 0) { |
| WriteStringToFd("No option is given.\n", fd); |
| cmdHelp(fd); |
| return; |
| } |
| |
| const std::string option = options[0]; |
| if (EqualsIgnoreCase(option, "--help")) { |
| cmdHelp(fd); |
| } else if (EqualsIgnoreCase(option, "--list")) { |
| cmdList(fd, options); |
| } else if (EqualsIgnoreCase(option, "--dump")) { |
| cmdDumpDevice(fd, options); |
| } else if (EqualsIgnoreCase(option, "--configure-emulated-camera")) { |
| cmdConfigureEmulatedCamera(fd, options); |
| } else { |
| WriteStringToFd(StringPrintf("Invalid option: %s\n", option.c_str()), |
| fd); |
| } |
| } |
| |
| |
| void Enumerator::cmdHelp(int fd) { |
| WriteStringToFd("--help: shows this help.\n" |
| "--list [all|camera|display]: lists camera or display devices or both " |
| "available to EVS manager.\n" |
| "--dump camera [all|device_id] --[current|collected|custom] [args]\n" |
| "\tcurrent: shows the current status\n" |
| "\tcollected: shows 10 most recent periodically collected camera usage " |
| "statistics\n" |
| "\tcustom: starts/stops collecting the camera usage statistics\n" |
| "\t\tstart [interval] [duration]: starts collecting usage statistics " |
| "at every [interval] during [duration]. Interval and duration are in " |
| "milliseconds.\n" |
| "\t\tstop: stops collecting usage statistics and shows collected records.\n" |
| "--dump display: shows current status of the display\n" |
| "--configure-emulated-camera [id] [path] [width] [height] [interval]\n" |
| "\tid: emulated device id to use; emulated/[0-9]+\n" |
| "\tpath: a path to the directory where source files are stored\n" |
| "\twidth: image width in pixels\n" |
| "\theight: image height in pixels\n" |
| "\tinterval: interval between consecutive frames in milliseconds.\n", fd); |
| } |
| |
| |
| void Enumerator::cmdList(int fd, const hidl_vec<hidl_string>& options) { |
| bool listCameras = true; |
| bool listDisplays = true; |
| if (options.size() > 1) { |
| const std::string option = options[1]; |
| const bool listAll = EqualsIgnoreCase(option, kDumpOptionAll); |
| listCameras = listAll || EqualsIgnoreCase(option, kDumpDeviceCamera); |
| listDisplays = listAll || EqualsIgnoreCase(option, kDumpDeviceDisplay); |
| if (!listCameras && !listDisplays) { |
| WriteStringToFd(StringPrintf("Unrecognized option, %s, is ignored.\n", |
| option.c_str()), |
| fd); |
| |
| // Nothing to show, return |
| return; |
| } |
| } |
| |
| std::string buffer; |
| if (listCameras) { |
| StringAppendF(&buffer,"Camera devices available to EVS service:\n"); |
| if (mCameraDevices.size() < 1) { |
| // Camera devices may not be enumerated yet. This may fail if the |
| // user is not permitted to use EVS service. |
| getCameraList_1_1( |
| [](const auto cameras) { |
| if (cameras.size() < 1) { |
| LOG(WARNING) << "No camera device is available to EVS."; |
| } |
| }); |
| } |
| |
| for (auto& [id, desc] : mCameraDevices) { |
| StringAppendF(&buffer, "%s%s\n", kSingleIndent, id.c_str()); |
| } |
| |
| StringAppendF(&buffer, "%sCamera devices currently in use:\n", kSingleIndent); |
| for (auto& [id, ptr] : mActiveCameras) { |
| StringAppendF(&buffer, "%s%s\n", kSingleIndent, id.c_str()); |
| } |
| StringAppendF(&buffer, "\n"); |
| } |
| |
| if (listDisplays) { |
| if (mHwEnumerator != nullptr) { |
| StringAppendF(&buffer, "Display devices available to EVS service:\n"); |
| // Get an internal display identifier. |
| mHwEnumerator->getDisplayIdList( |
| [&](const auto& displayPorts) { |
| for (auto&& port : displayPorts) { |
| StringAppendF(&buffer, "%sdisplay port %u\n", |
| kSingleIndent, |
| static_cast<unsigned>(port)); |
| } |
| } |
| ); |
| } else { |
| LOG(WARNING) << "EVS HAL implementation is not available."; |
| } |
| } |
| |
| WriteStringToFd(buffer, fd); |
| } |
| |
| |
| void Enumerator::cmdDumpDevice(int fd, const hidl_vec<hidl_string>& options) { |
| // Dumps both cameras and displays if the target device type is not given |
| bool dumpCameras = false; |
| bool dumpDisplays = false; |
| const auto numOptions = options.size(); |
| if (numOptions > kOptionDumpDeviceTypeIndex) { |
| const std::string target = options[kOptionDumpDeviceTypeIndex]; |
| dumpCameras = EqualsIgnoreCase(target, kDumpDeviceCamera); |
| dumpDisplays = EqualsIgnoreCase(target, kDumpDeviceDisplay); |
| if (!dumpCameras && !dumpDisplays) { |
| WriteStringToFd(StringPrintf("Unrecognized option, %s, is ignored.\n", |
| target.c_str()), |
| fd); |
| cmdHelp(fd); |
| return; |
| } |
| } else { |
| WriteStringToFd(StringPrintf("Necessary arguments are missing. " |
| "Please check the usages:\n"), |
| fd); |
| cmdHelp(fd); |
| return; |
| } |
| |
| if (dumpCameras) { |
| // --dump camera [all|device_id] --[current|collected|custom] [args] |
| if (numOptions < kDumpCameraMinNumArgs) { |
| WriteStringToFd(StringPrintf("Necessary arguments are missing. " |
| "Please check the usages:\n"), |
| fd); |
| cmdHelp(fd); |
| return; |
| } |
| |
| const std::string deviceId = options[kOptionDumpCameraTypeIndex]; |
| auto target = mActiveCameras.find(deviceId); |
| const bool dumpAllCameras = EqualsIgnoreCase(deviceId, |
| kDumpOptionAll); |
| if (!dumpAllCameras && target == mActiveCameras.end()) { |
| // Unknown camera identifier |
| WriteStringToFd(StringPrintf("Given camera ID %s is unknown or not active.\n", |
| deviceId.c_str()), |
| fd); |
| return; |
| } |
| |
| const std::string command = options[kOptionDumpCameraCommandIndex]; |
| std::string cameraInfo; |
| if (EqualsIgnoreCase(command, kDumpCameraCommandCurrent)) { |
| // Active stream configuration from each active HalCamera objects |
| if (!dumpAllCameras) { |
| StringAppendF(&cameraInfo, "HalCamera: %s\n%s", |
| deviceId.c_str(), |
| target->second->toString(kSingleIndent).c_str()); |
| } else { |
| for (auto&& [id, handle] : mActiveCameras) { |
| // Appends the current status |
| cameraInfo += handle->toString(kSingleIndent); |
| } |
| } |
| } else if (EqualsIgnoreCase(command, kDumpCameraCommandCollected)) { |
| // Reads the usage statistics from active HalCamera objects |
| std::unordered_map<std::string, std::string> usageStrings; |
| if (mMonitorEnabled) { |
| auto result = mClientsMonitor->toString(&usageStrings, kSingleIndent); |
| if (!result.ok()) { |
| LOG(ERROR) << "Failed to get the monitoring result"; |
| return; |
| } |
| |
| if (!dumpAllCameras) { |
| cameraInfo += usageStrings[deviceId]; |
| } else { |
| for (auto&& [id, stats] : usageStrings) { |
| cameraInfo += stats; |
| } |
| } |
| } else { |
| WriteStringToFd(StringPrintf("Client monitor is not available.\n"), |
| fd); |
| return; |
| } |
| } else if (EqualsIgnoreCase(command, kDumpCameraCommandCustom)) { |
| // Additional arguments are expected for this command: |
| // --dump camera device_id --custom start [interval] [duration] |
| // or, --dump camera device_id --custom stop |
| if (numOptions < kDumpCameraMinNumArgs + 1) { |
| WriteStringToFd(StringPrintf("Necessary arguments are missing. " |
| "Please check the usages:\n"), |
| fd); |
| cmdHelp(fd); |
| return; |
| } |
| |
| if (!mMonitorEnabled) { |
| WriteStringToFd(StringPrintf("Client monitor is not available."), fd); |
| return; |
| } |
| |
| const std::string subcommand = options[kOptionDumpCameraArgsStartIndex]; |
| if (EqualsIgnoreCase(subcommand, kDumpCameraCommandCustomStart)) { |
| using std::chrono::nanoseconds; |
| using std::chrono::milliseconds; |
| using std::chrono::duration_cast; |
| nanoseconds interval = 0ns; |
| nanoseconds duration = 0ns; |
| if (numOptions > kOptionDumpCameraArgsStartIndex + 2) { |
| duration = duration_cast<nanoseconds>( |
| milliseconds( |
| std::stoi(options[kOptionDumpCameraArgsStartIndex + 2]) |
| )); |
| } |
| |
| if (numOptions > kOptionDumpCameraArgsStartIndex + 1) { |
| interval = duration_cast<nanoseconds>( |
| milliseconds( |
| std::stoi(options[kOptionDumpCameraArgsStartIndex + 1]) |
| )); |
| } |
| |
| // Starts a custom collection |
| auto result = mClientsMonitor->startCustomCollection(interval, duration); |
| if (!result.ok()) { |
| LOG(ERROR) << "Failed to start a custom collection. " |
| << result.error(); |
| StringAppendF(&cameraInfo, "Failed to start a custom collection. %s\n", |
| result.error().message().c_str()); |
| } |
| } else if (EqualsIgnoreCase(subcommand, kDumpCameraCommandCustomStop)) { |
| if (!mMonitorEnabled) { |
| WriteStringToFd(StringPrintf("Client monitor is not available."), fd); |
| return; |
| } |
| |
| auto result = mClientsMonitor->stopCustomCollection(deviceId); |
| if (!result.ok()) { |
| LOG(ERROR) << "Failed to stop a custom collection. " |
| << result.error(); |
| StringAppendF(&cameraInfo, "Failed to stop a custom collection. %s\n", |
| result.error().message().c_str()); |
| } else { |
| // Pull the custom collection |
| cameraInfo += *result; |
| } |
| } else { |
| WriteStringToFd(StringPrintf("Unknown argument: %s\n", |
| subcommand.c_str()), |
| fd); |
| cmdHelp(fd); |
| return; |
| } |
| } else { |
| WriteStringToFd(StringPrintf("Unknown command: %s\n" |
| "Please check the usages:\n", command.c_str()), |
| fd); |
| cmdHelp(fd); |
| return; |
| } |
| |
| // Outputs the report |
| WriteStringToFd(cameraInfo, fd); |
| } |
| |
| if (dumpDisplays) { |
| HalDisplay* pDisplay = |
| reinterpret_cast<HalDisplay*>(mActiveDisplay.promote().get()); |
| if (!pDisplay) { |
| WriteStringToFd("No active display is found.\n", fd); |
| } else { |
| WriteStringToFd(pDisplay->toString(kSingleIndent), fd); |
| } |
| } |
| } |
| |
| |
| void Enumerator::cmdConfigureEmulatedCamera(int fd, const hidl_vec<hidl_string>& options) { |
| if (options.size() < 6) { |
| WriteStringToFd(StringPrintf("Necessary arguments are missing.\n"), fd); |
| cmdHelp(fd); |
| return; |
| } |
| |
| // --configure-emulated-camera [id] [path] [width] [height] [interval] |
| const std::string id = options[1]; |
| if (!std::regex_match(id.c_str(), kEmulatedCameraNamePattern)) { |
| WriteStringToFd(StringPrintf("%s does not match to the pattern.\n", id.c_str()), fd); |
| return; |
| } |
| |
| if (mCameraDevices.find(id) != mCameraDevices.end()) { |
| WriteStringToFd( |
| StringPrintf("Updating %s's configuration. " |
| "This will get effective when currently active stream is closed.\n", |
| id.c_str()), fd); |
| } |
| |
| std::string sourceDir = options[2]; |
| int width = std::stoi(options[3]); |
| int height = std::stoi(options[4]); |
| std::chrono::nanoseconds interval = std::chrono::duration_cast<std::chrono::nanoseconds>( |
| std::chrono::milliseconds(std::stoi(options[5])) |
| ); |
| WriteStringToFd(StringPrintf("Configuring %s as:\n" |
| "\tResolution: %dx%d\n" |
| "\tInterval: %f ms\n", |
| id.c_str(), width, height, |
| interval.count() / 1000000.), fd); |
| |
| EmulatedCameraDesc desc = {width, height, sourceDir, interval}; |
| mEmulatedCameraDevices.insert_or_assign(id, std::move(desc)); |
| } |
| |
| |
| } // namespace implementation |
| } // namespace V1_1 |
| } // namespace evs |
| } // namespace automotive |
| } // namespace android |