/* * 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. */ #define LOG_TAG "ExtCamPrvdr" // #define LOG_NDEBUG 0 #include "ExternalCameraProvider.h" #include #include #include #include #include #include #include #include namespace android { namespace hardware { namespace camera { namespace provider { namespace implementation { using ::aidl::android::hardware::camera::common::Status; using ::android::hardware::camera::device::implementation::ExternalCameraDevice; using ::android::hardware::camera::device::implementation::fromStatus; using ::android::hardware::camera::external::common::ExternalCameraConfig; namespace { // "device@/external/" const std::regex kDeviceNameRE("device@([0-9]+\\.[0-9]+)/external/(.+)"); const int kMaxDevicePathLen = 256; constexpr char kDevicePath[] = "/dev/"; constexpr char kPrefix[] = "video"; constexpr int kPrefixLen = sizeof(kPrefix) - 1; constexpr int kDevicePrefixLen = sizeof(kDevicePath) + kPrefixLen - 1; bool matchDeviceName(int cameraIdOffset, const std::string& deviceName, std::string* deviceVersion, std::string* cameraDevicePath) { std::smatch sm; if (std::regex_match(deviceName, sm, kDeviceNameRE)) { if (deviceVersion != nullptr) { *deviceVersion = sm[1]; } if (cameraDevicePath != nullptr) { *cameraDevicePath = "/dev/video" + std::to_string(std::stoi(sm[2]) - cameraIdOffset); } return true; } return false; } } // namespace ExternalCameraProvider::ExternalCameraProvider() : mCfg(ExternalCameraConfig::loadFromCfg()) { mHotPlugThread = std::make_shared(this); mHotPlugThread->run(); } ExternalCameraProvider::~ExternalCameraProvider() { mHotPlugThread->requestExitAndWait(); } ndk::ScopedAStatus ExternalCameraProvider::setCallback( const std::shared_ptr& in_callback) { if (in_callback == nullptr) { return fromStatus(Status::ILLEGAL_ARGUMENT); } { Mutex::Autolock _l(mLock); mCallback = in_callback; } for (const auto& pair : mCameraStatusMap) { mCallback->cameraDeviceStatusChange(pair.first, pair.second); } return fromStatus(Status::OK); } ndk::ScopedAStatus ExternalCameraProvider::getVendorTags( std::vector* _aidl_return) { if (_aidl_return == nullptr) { return fromStatus(Status::ILLEGAL_ARGUMENT); } // No vendor tag support for USB camera *_aidl_return = {}; return fromStatus(Status::OK); } ndk::ScopedAStatus ExternalCameraProvider::getCameraIdList(std::vector* _aidl_return) { if (_aidl_return == nullptr) { return fromStatus(Status::ILLEGAL_ARGUMENT); } // External camera HAL always report 0 camera, and extra cameras // are just reported via cameraDeviceStatusChange callbacks *_aidl_return = {}; return fromStatus(Status::OK); } ndk::ScopedAStatus ExternalCameraProvider::getCameraDeviceInterface( const std::string& in_cameraDeviceName, std::shared_ptr* _aidl_return) { if (_aidl_return == nullptr) { return fromStatus(Status::ILLEGAL_ARGUMENT); } std::string cameraDevicePath, deviceVersion; bool match = matchDeviceName(mCfg.cameraIdOffset, in_cameraDeviceName, &deviceVersion, &cameraDevicePath); if (!match) { *_aidl_return = nullptr; return fromStatus(Status::ILLEGAL_ARGUMENT); } if (mCameraStatusMap.count(in_cameraDeviceName) == 0 || mCameraStatusMap[in_cameraDeviceName] != CameraDeviceStatus::PRESENT) { *_aidl_return = nullptr; return fromStatus(Status::ILLEGAL_ARGUMENT); } ALOGV("Constructing external camera device"); std::shared_ptr deviceImpl = ndk::SharedRefBase::make(cameraDevicePath, mCfg); if (deviceImpl == nullptr || deviceImpl->isInitFailed()) { ALOGE("%s: camera device %s init failed!", __FUNCTION__, cameraDevicePath.c_str()); *_aidl_return = nullptr; return fromStatus(Status::INTERNAL_ERROR); } IF_ALOGV() { int interfaceVersion; deviceImpl->getInterfaceVersion(&interfaceVersion); ALOGV("%s: device interface version: %d", __FUNCTION__, interfaceVersion); } *_aidl_return = deviceImpl; return fromStatus(Status::OK); } ndk::ScopedAStatus ExternalCameraProvider::notifyDeviceStateChange(int64_t) { return fromStatus(Status::OK); } ndk::ScopedAStatus ExternalCameraProvider::getConcurrentCameraIds( std::vector* _aidl_return) { if (_aidl_return == nullptr) { return fromStatus(Status::ILLEGAL_ARGUMENT); } *_aidl_return = {}; return fromStatus(Status::OK); } ndk::ScopedAStatus ExternalCameraProvider::isConcurrentStreamCombinationSupported( const std::vector&, bool* _aidl_return) { if (_aidl_return == nullptr) { return fromStatus(Status::ILLEGAL_ARGUMENT); } // No concurrent stream combinations are supported *_aidl_return = false; return fromStatus(Status::OK); } void ExternalCameraProvider::addExternalCamera(const char* devName) { ALOGV("%s: ExtCam: adding %s to External Camera HAL!", __FUNCTION__, devName); Mutex::Autolock _l(mLock); std::string deviceName; std::string cameraId = std::to_string(mCfg.cameraIdOffset + std::atoi(devName + kDevicePrefixLen)); deviceName = std::string("device@") + ExternalCameraDevice::kDeviceVersion + "/external/" + cameraId; mCameraStatusMap[deviceName] = CameraDeviceStatus::PRESENT; if (mCallback != nullptr) { mCallback->cameraDeviceStatusChange(deviceName, CameraDeviceStatus::PRESENT); } } void ExternalCameraProvider::deviceAdded(const char* devName) { { base::unique_fd fd(::open(devName, O_RDWR)); if (fd.get() < 0) { ALOGE("%s open v4l2 device %s failed:%s", __FUNCTION__, devName, strerror(errno)); return; } struct v4l2_capability capability; int ret = ioctl(fd.get(), VIDIOC_QUERYCAP, &capability); if (ret < 0) { ALOGE("%s v4l2 QUERYCAP %s failed", __FUNCTION__, devName); return; } if (!(capability.device_caps & V4L2_CAP_VIDEO_CAPTURE)) { ALOGW("%s device %s does not support VIDEO_CAPTURE", __FUNCTION__, devName); return; } } // See if we can initialize ExternalCameraDevice correctly std::shared_ptr deviceImpl = ndk::SharedRefBase::make(devName, mCfg); if (deviceImpl == nullptr || deviceImpl->isInitFailed()) { ALOGW("%s: Attempt to init camera device %s failed!", __FUNCTION__, devName); return; } deviceImpl.reset(); addExternalCamera(devName); } void ExternalCameraProvider::deviceRemoved(const char* devName) { Mutex::Autolock _l(mLock); std::string deviceName; std::string cameraId = std::to_string(mCfg.cameraIdOffset + std::atoi(devName + kDevicePrefixLen)); deviceName = std::string("device@") + ExternalCameraDevice::kDeviceVersion + "/external/" + cameraId; if (mCameraStatusMap.erase(deviceName) == 0) { // Unknown device, do not fire callback ALOGE("%s: cannot find camera device to remove %s", __FUNCTION__, devName); return; } if (mCallback != nullptr) { mCallback->cameraDeviceStatusChange(deviceName, CameraDeviceStatus::NOT_PRESENT); } } void ExternalCameraProvider::updateAttachedCameras() { ALOGV("%s start scanning for existing V4L2 devices", __FUNCTION__); // Find existing /dev/video* devices DIR* devdir = opendir(kDevicePath); if (devdir == nullptr) { ALOGE("%s: cannot open %s! Exiting threadloop", __FUNCTION__, kDevicePath); return; } struct dirent* de; while ((de = readdir(devdir)) != nullptr) { // Find external v4l devices that's existing before we start watching and add them if (!strncmp(kPrefix, de->d_name, kPrefixLen)) { std::string deviceId(de->d_name + kPrefixLen); if (mCfg.mInternalDevices.count(deviceId) == 0) { ALOGV("Non-internal v4l device %s found", de->d_name); char v4l2DevicePath[kMaxDevicePathLen]; snprintf(v4l2DevicePath, kMaxDevicePathLen, "%s%s", kDevicePath, de->d_name); deviceAdded(v4l2DevicePath); } } } closedir(devdir); } // Start ExternalCameraProvider::HotplugThread functions ExternalCameraProvider::HotplugThread::HotplugThread(ExternalCameraProvider* parent) : mParent(parent), mInternalDevices(parent->mCfg.mInternalDevices) {} ExternalCameraProvider::HotplugThread::~HotplugThread() { // Clean up inotify descriptor if needed. if (mINotifyFD >= 0) { close(mINotifyFD); } } bool ExternalCameraProvider::HotplugThread::initialize() { // Update existing cameras mParent->updateAttachedCameras(); // Set up non-blocking fd. The threadLoop will be responsible for polling read at the // desired frequency mINotifyFD = inotify_init(); if (mINotifyFD < 0) { ALOGE("%s: inotify init failed! Exiting threadloop", __FUNCTION__); return false; } // Start watching /dev/ directory for created and deleted files mWd = inotify_add_watch(mINotifyFD, kDevicePath, IN_CREATE | IN_DELETE); if (mWd < 0) { ALOGE("%s: inotify add watch failed! Exiting threadloop", __FUNCTION__); return false; } mPollFd = {.fd = mINotifyFD, .events = POLLIN}; mIsInitialized = true; return true; } bool ExternalCameraProvider::HotplugThread::threadLoop() { // Initialize inotify descriptors if needed. if (!mIsInitialized && !initialize()) { return true; } // poll /dev/* and handle timeouts and error int pollRet = poll(&mPollFd, /* fd_count= */ 1, /* timeout= */ 250); if (pollRet == 0) { // no read event in 100ms mPollFd.revents = 0; return true; } else if (pollRet < 0) { ALOGE("%s: error while polling for /dev/*: %d", __FUNCTION__, errno); mPollFd.revents = 0; return true; } else if (mPollFd.revents & POLLERR) { ALOGE("%s: polling /dev/ returned POLLERR", __FUNCTION__); mPollFd.revents = 0; return true; } else if (mPollFd.revents & POLLHUP) { ALOGE("%s: polling /dev/ returned POLLHUP", __FUNCTION__); mPollFd.revents = 0; return true; } else if (mPollFd.revents & POLLNVAL) { ALOGE("%s: polling /dev/ returned POLLNVAL", __FUNCTION__); mPollFd.revents = 0; return true; } // mPollFd.revents must contain POLLIN, so safe to reset it before reading mPollFd.revents = 0; uint64_t offset = 0; ssize_t ret = read(mINotifyFD, mEventBuf, sizeof(mEventBuf)); if (ret < sizeof(struct inotify_event)) { // invalid event. skip return true; } while (offset < ret) { struct inotify_event* event = (struct inotify_event*)&mEventBuf[offset]; offset += sizeof(struct inotify_event) + event->len; if (event->wd != mWd) { // event for an unrelated descriptor. ignore. continue; } ALOGV("%s inotify_event %s", __FUNCTION__, event->name); if (strncmp(kPrefix, event->name, kPrefixLen) != 0) { // event not for /dev/video*. ignore. continue; } std::string deviceId = event->name + kPrefixLen; if (mInternalDevices.count(deviceId) != 0) { // update to an internal device. ignore. continue; } char v4l2DevicePath[kMaxDevicePathLen]; snprintf(v4l2DevicePath, kMaxDevicePathLen, "%s%s", kDevicePath, event->name); if (event->mask & IN_CREATE) { mParent->deviceAdded(v4l2DevicePath); } else if (event->mask & IN_DELETE) { mParent->deviceRemoved(v4l2DevicePath); } } return true; } // End ExternalCameraProvider::HotplugThread functions } // namespace implementation } // namespace provider } // namespace camera } // namespace hardware } // namespace android