diff --git a/automotive/evs/aidl/impl/default/Android.bp b/automotive/evs/aidl/impl/default/Android.bp index dbe0314118..bf6c0be236 100644 --- a/automotive/evs/aidl/impl/default/Android.bp +++ b/automotive/evs/aidl/impl/default/Android.bp @@ -24,13 +24,56 @@ package { cc_binary { name: "android.hardware.automotive.evs-aidl-default-service", defaults: ["EvsHalDefaults"], - local_include_dirs: ["include"], - vintf_fragments: ["evs-default-service.xml"], + vintf_fragments: ["manifest_evs-default-service.xml"], init_rc: ["evs-default-service.rc"], vendor: true, relative_install_path: "hw", - srcs: ["src/*.cpp"], - shared_libs: [ - "libbinder_ndk", + cflags: [ + "-DGL_GLEXT_PROTOTYPES", + "-DEGL_EGLEXT_PROTOTYPES", + "-Wall", + "-Wextra", + "-Werror", + "-Wthread-safety", ], + srcs: [ + ":libgui_frame_event_aidl", + "src/*.cpp" + ], + shared_libs: [ + "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + "android.hidl.token@1.0-utils", + "libEGL", + "libGLESv2", + "libbase", + "libbinder_ndk", + "libbufferqueueconverter", + "libcamera_metadata", + "libhardware_legacy", + "libhidlbase", + "liblog", + "libnativewindow", + "libtinyxml2", + "libui", + "libutils", + "libyuv", + ], + static_libs: [ + "android.frameworks.automotive.display-V1-ndk", + "android.hardware.automotive.evs-V1-ndk", + "android.hardware.common-V2-ndk", + "libaidlcommonsupport", + "libcutils", + ], + local_include_dirs: ["include"], + include_dirs: ["frameworks/native/include/"], + required: ["evs_mock_hal_configuration.xml"], +} + +prebuilt_etc { + name: "evs_mock_hal_configuration.xml", + soc_specific: true, + src: "resources/evs_mock_configuration.xml", + sub_dir: "automotive/evs", } diff --git a/automotive/evs/aidl/impl/default/evs-default-service.rc b/automotive/evs/aidl/impl/default/evs-default-service.rc index ea8e6892dc..3da41ff787 100644 --- a/automotive/evs/aidl/impl/default/evs-default-service.rc +++ b/automotive/evs/aidl/impl/default/evs-default-service.rc @@ -1,5 +1,8 @@ service vendor.evs-hal-default /vendor/bin/hw/android.hardware.automotive.evs-aidl-default-service class early_hal - user automotive_evs - group automotive_evs + priority -20 + user graphics + group automotive_evs camera + onrestart restart cardisplayproxyd + onrestart restart evsmanagerd disabled diff --git a/automotive/evs/aidl/impl/default/evs-default-service.xml b/automotive/evs/aidl/impl/default/evs-default-service.xml deleted file mode 100644 index 96ff9f6576..0000000000 --- a/automotive/evs/aidl/impl/default/evs-default-service.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - android.hardware.automotive.evs - hwbinder - 1 - - IEvsEnumerator - hw/0 - - - diff --git a/automotive/evs/aidl/impl/default/include/ConfigManager.h b/automotive/evs/aidl/impl/default/include/ConfigManager.h new file mode 100644 index 0000000000..1d5fe772b2 --- /dev/null +++ b/automotive/evs/aidl/impl/default/include/ConfigManager.h @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "ConfigManagerUtil.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +/* + * 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 { + int id; + int width; + int height; + ::aidl::android::hardware::graphics::common::PixelFormat format; + int type; + int framerate; +} StreamConfiguration; + +class ConfigManager final { + public: + static std::unique_ptr Create(); + ConfigManager(const ConfigManager&) = delete; + ConfigManager& operator=(const ConfigManager&) = delete; + + /* Camera device's capabilities and metadata */ + class CameraInfo { + public: + CameraInfo() : characteristics(nullptr) {} + + virtual ~CameraInfo(); + + /* Allocate memory for camera_metadata_t */ + bool allocate(size_t entry_cap, size_t data_cap) { + if (characteristics != nullptr) { + LOG(ERROR) << "Camera metadata is already allocated"; + return false; + } + + characteristics = allocate_camera_metadata(entry_cap, data_cap); + return characteristics != nullptr; + } + + /* + * List of supported controls that the primary client can program. + * Paraemters are stored with its valid range + */ + std::unordered_map<::aidl::android::hardware::automotive::evs::CameraParam, + std::tuple> + controls; + + /* + * List of supported output stream configurations. + */ + std::unordered_map streamConfigurations; + + /* + * Internal storage for camera metadata. Each entry holds a pointer to + * data and number of elements + */ + std::unordered_map> cameraMetadata; + + /* Camera module characteristics */ + camera_metadata_t* characteristics; + }; + + class CameraGroupInfo : public CameraInfo { + public: + CameraGroupInfo() {} + + /* ID of member camera devices */ + std::unordered_set devices; + + /* The capture operation of member camera devices are synchronized */ + int32_t synchronized = 0; + }; + + class SystemInfo { + public: + /* number of available cameras */ + int32_t numCameras = 0; + }; + + class DisplayInfo { + public: + /* + * List of supported input stream configurations. + */ + std::unordered_map streamConfigurations; + }; + + /* + * Return system information + * + * @return SystemInfo + * Constant reference of SystemInfo. + */ + const SystemInfo& getSystemInfo() { + std::unique_lock lock(mConfigLock); + mConfigCond.wait(lock, [this] { return mIsReady; }); + return mSystemInfo; + } + + /* + * Return a list of camera identifiers + * + * This function assumes that it is not being called frequently. + * + * @return std::vector + * A vector that contains unique camera device identifiers. + */ + std::vector getCameraIdList() { + std::unique_lock lock(mConfigLock); + mConfigCond.wait(lock, [this] { return mIsReady; }); + + std::vector aList; + aList.reserve(mCameraInfo.size()); + for (auto&& v : mCameraInfo) { + aList.push_back(v.first); + } + + return aList; + } + + /* + * Return a list of camera group identifiers + * + * This function assumes that it is not being called frequently. + * + * @return std::vector + * A vector that contains unique camera device identifiers. + */ + std::vector getCameraGroupIdList() { + std::unique_lock lock(mConfigLock); + mConfigCond.wait(lock, [this] { return mIsReady; }); + + std::vector aList; + aList.reserve(mCameraGroups.size()); + for (auto&& v : mCameraGroups) { + aList.push_back(v.first); + } + + return aList; + } + + /* + * Return a pointer to the camera group + * + * @return CameraGroup + * A pointer to a camera group identified by a given id. + */ + std::unique_ptr& getCameraGroupInfo(const std::string& gid) { + std::unique_lock lock(mConfigLock); + mConfigCond.wait(lock, [this] { return mIsReady; }); + + return mCameraGroups[gid]; + } + + /* + * Return a camera metadata + * + * @param cameraId + * Unique camera node identifier in string + * + * @return unique_ptr + * A pointer to CameraInfo that is associated with a given camera + * ID. This returns a null pointer if this does not recognize a + * given camera identifier. + */ + std::unique_ptr& getCameraInfo(const std::string& cameraId) noexcept { + std::unique_lock lock(mConfigLock); + mConfigCond.wait(lock, [this] { return mIsReady; }); + + return mCameraInfo[cameraId]; + } + + /* + * Tell whether the configuration data is ready to be used + * + * @return bool + * True if configuration data is ready to be consumed. + */ + bool isReady() const { return mIsReady; } + + private: + /* Constructors */ + ConfigManager() : mBinaryFilePath("") {} + + static std::string_view sConfigDefaultPath; + static std::string_view sConfigOverridePath; + + /* System configuration */ + SystemInfo mSystemInfo; + + /* Internal data structure for camera device information */ + std::unordered_map> mCameraInfo; + + /* Internal data structure for camera device information */ + std::unordered_map> mDisplayInfo; + + /* Camera groups are stored in hash map */ + std::unordered_map> mCameraGroups; + + /* + * Camera positions are stored in hash map. + * The position must be one of front, rear, left, and right. + */ + std::unordered_map> mCameraPosition; + + /* Configuration data lock */ + mutable std::mutex mConfigLock; + + /* + * This condition is signalled when it completes a configuration data + * preparation. + */ + std::condition_variable mConfigCond; + + /* A path to a binary configuration file */ + const char* mBinaryFilePath; + + /* Configuration data readiness */ + bool mIsReady = false; + + /* + * Parse a given EVS configuration file and store the information + * internally. + * + * @return bool + * True if it completes parsing a file successfully. + */ + bool readConfigDataFromXML() noexcept; + + /* + * read the information of the vehicle + * + * @param aSysElem + * A pointer to "system" XML element. + */ + void readSystemInfo(const tinyxml2::XMLElement* const aSysElem); + + /* + * read the information of camera devices + * + * @param aCameraElem + * A pointer to "camera" XML element that may contain multiple + * "device" elements. + */ + void readCameraInfo(const tinyxml2::XMLElement* const aCameraElem); + + /* + * read display device information + * + * @param aDisplayElem + * A pointer to "display" XML element that may contain multiple + * "device" elements. + */ + void readDisplayInfo(const tinyxml2::XMLElement* const aDisplayElem); + + /* + * read camera device information + * + * @param aCamera + * A pointer to CameraInfo that will be completed by this + * method. + * aDeviceElem + * A pointer to "device" XML element that contains camera module + * capability info and its characteristics. + * + * @return bool + * Return false upon any failure in reading and processing camera + * device information. + */ + bool readCameraDeviceInfo(CameraInfo* aCamera, const tinyxml2::XMLElement* aDeviceElem); + + /* + * read camera metadata + * + * @param aCapElem + * A pointer to "cap" XML element. + * @param aCamera + * A pointer to CameraInfo that is being filled by this method. + * @param dataSize + * Required size of memory to store camera metadata found in this + * method. This is calculated in this method and returned to the + * caller for camera_metadata allocation. + * + * @return size_t + * Number of camera metadata entries + */ + size_t readCameraCapabilities(const tinyxml2::XMLElement* const aCapElem, CameraInfo* aCamera, + size_t& dataSize); + + /* + * read camera metadata + * + * @param aParamElem + * A pointer to "characteristics" XML element. + * @param aCamera + * A pointer to CameraInfo that is being filled by this method. + * @param dataSize + * Required size of memory to store camera metadata found in this + * method. + * + * @return size_t + * Number of camera metadata entries + */ + size_t readCameraMetadata(const tinyxml2::XMLElement* const aParamElem, CameraInfo* aCamera, + size_t& dataSize); + + /* + * construct camera_metadata_t from camera capabilities and metadata + * + * @param aCamera + * A pointer to CameraInfo that is being filled by this method. + * @param totalEntries + * Number of camera metadata entries to be added. + * @param totalDataSize + * Sum of sizes of camera metadata entries to be added. + * + * @return bool + * False if either it fails to allocate memory for camera metadata + * or its size is not large enough to add all found camera metadata + * entries. + */ + bool constructCameraMetadata(CameraInfo* aCamera, const size_t totalEntries, + const size_t totalDataSize); + + /* + * Read configuration data from the binary file + * + * @return bool + * True if it succeeds to read configuration data from a binary + * file. + */ + bool readConfigDataFromBinary(); + + /* + * Store configuration data to the file + * + * @return bool + * True if it succeeds to serialize mCameraInfo to the file. + */ + bool writeConfigDataToBinary(); + + /* + * debugging method to print out all XML elements and their attributes in + * logcat message. + * + * @param aNode + * A pointer to the root XML element to navigate. + * @param prefix + * A prefix to XML string. + */ + void printElementNames(const tinyxml2::XMLElement* aNode, const std::string& prefix = "") const; +}; diff --git a/automotive/evs/aidl/impl/default/include/ConfigManagerUtil.h b/automotive/evs/aidl/impl/default/include/ConfigManagerUtil.h new file mode 100644 index 0000000000..32b50d3332 --- /dev/null +++ b/automotive/evs/aidl/impl/default/include/ConfigManagerUtil.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +class ConfigManagerUtil final { + public: + /** + * Convert a given string into V4L2_CID_* + */ + static bool convertToEvsCameraParam( + const std::string& id, + ::aidl::android::hardware::automotive::evs::CameraParam& camParam); + /** + * Convert a given string into android.hardware.graphics.common.PixelFormat + */ + static bool convertToPixelFormat(const std::string& in, + ::aidl::android::hardware::graphics::common::PixelFormat& out); + /** + * Convert a given string into corresponding camera metadata data tag defined in + * system/media/camera/include/system/camera_metadata_tags.h + */ + static bool convertToMetadataTag(const char* name, camera_metadata_tag& aTag); + /** + * Convert a given string into a floating value array + */ + static float* convertFloatArray(const char* sz, const char* vals, size_t& count, + const char delimiter = ','); + /** + * Trim a string + */ + static std::string trimString(const std::string& src, const std::string& ws = " \n\r\t\f\v"); + + /** + * Convert a given string to corresponding camera capabilities + */ + static bool convertToCameraCapability( + const char* name, camera_metadata_enum_android_request_available_capabilities_t& cap); + + DISALLOW_IMPLICIT_CONSTRUCTORS(ConfigManagerUtil); +}; diff --git a/automotive/evs/aidl/impl/default/include/DefaultEvsEnumerator.h b/automotive/evs/aidl/impl/default/include/DefaultEvsEnumerator.h deleted file mode 100644 index 03a578d954..0000000000 --- a/automotive/evs/aidl/impl/default/include/DefaultEvsEnumerator.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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. - */ - -#ifndef android_hardware_automotive_evs_aidl_impl_evshal_include_DefaultEvsHal_H_ -#define android_hardware_automotive_evs_aidl_impl_evshal_include_DefaultEvsHal_H_ - -#include - -namespace aidl::android::hardware::automotive::evs::implementation { - -class DefaultEvsEnumerator final - : public ::aidl::android::hardware::automotive::evs::BnEvsEnumerator { - ::ndk::ScopedAStatus isHardware(bool* flag) override; - ::ndk::ScopedAStatus openCamera( - const std::string& cameraId, - const ::aidl::android::hardware::automotive::evs::Stream& streamConfig, - std::shared_ptr<::aidl::android::hardware::automotive::evs::IEvsCamera>* obj) override; - ::ndk::ScopedAStatus closeCamera( - const std::shared_ptr<::aidl::android::hardware::automotive::evs::IEvsCamera>& obj) - override; - ::ndk::ScopedAStatus getCameraList( - std::vector<::aidl::android::hardware::automotive::evs::CameraDesc>* list) override; - ::ndk::ScopedAStatus getStreamList( - const ::aidl::android::hardware::automotive::evs::CameraDesc& desc, - std::vector<::aidl::android::hardware::automotive::evs::Stream>* _aidl_return) override; - ::ndk::ScopedAStatus openDisplay( - int32_t displayId, - std::shared_ptr<::aidl::android::hardware::automotive::evs::IEvsDisplay>* obj) override; - ::ndk::ScopedAStatus closeDisplay( - const std::shared_ptr<::aidl::android::hardware::automotive::evs::IEvsDisplay>& obj) - override; - ::ndk::ScopedAStatus getDisplayIdList(std::vector* list) override; - ::ndk::ScopedAStatus getDisplayState( - ::aidl::android::hardware::automotive::evs::DisplayState* state) override; - ::ndk::ScopedAStatus registerStatusCallback( - const std::shared_ptr< - ::aidl::android::hardware::automotive::evs::IEvsEnumeratorStatusCallback>& - callback) override; - ::ndk::ScopedAStatus openUltrasonicsArray( - const std::string& id, - std::shared_ptr<::aidl::android::hardware::automotive::evs::IEvsUltrasonicsArray>* obj) - override; - ::ndk::ScopedAStatus closeUltrasonicsArray( - const std::shared_ptr<::aidl::android::hardware::automotive::evs::IEvsUltrasonicsArray>& - arr) override; - ::ndk::ScopedAStatus getUltrasonicsArrayList( - std::vector<::aidl::android::hardware::automotive::evs::UltrasonicsArrayDesc>* list) - override; -}; - -} // namespace aidl::android::hardware::automotive::evs::implementation - -#endif // android_hardware_automotive_evs_aidl_impl_evshal_include_DefaultEvsHal_H_ diff --git a/automotive/evs/aidl/impl/default/include/EvsEnumerator.h b/automotive/evs/aidl/impl/default/include/EvsEnumerator.h new file mode 100644 index 0000000000..b11dd3ee80 --- /dev/null +++ b/automotive/evs/aidl/impl/default/include/EvsEnumerator.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "ConfigManager.h" +#include "EvsGlDisplay.h" +#include "EvsMockCamera.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace aidl::android::hardware::automotive::evs::implementation { + +class EvsEnumerator final : public ::aidl::android::hardware::automotive::evs::BnEvsEnumerator { + public: + // Methods from ::aidl::android::hardware::automotive::evs::IEvsEnumerator + ndk::ScopedAStatus isHardware(bool* flag) override; + ndk::ScopedAStatus openCamera(const std::string& cameraId, const evs::Stream& streamConfig, + std::shared_ptr* obj) override; + ndk::ScopedAStatus closeCamera(const std::shared_ptr& obj) override; + ndk::ScopedAStatus getCameraList(std::vector* _aidl_return) override; + ndk::ScopedAStatus getStreamList(const evs::CameraDesc& desc, + std::vector* _aidl_return) override; + ndk::ScopedAStatus openDisplay(int32_t displayId, + std::shared_ptr* obj) override; + ndk::ScopedAStatus closeDisplay(const std::shared_ptr& obj) override; + ndk::ScopedAStatus getDisplayIdList(std::vector* list) override; + ndk::ScopedAStatus getDisplayState(evs::DisplayState* state) override; + ndk::ScopedAStatus registerStatusCallback( + const std::shared_ptr& callback) override; + ndk::ScopedAStatus openUltrasonicsArray( + const std::string& id, std::shared_ptr* obj) override; + ndk::ScopedAStatus closeUltrasonicsArray( + const std::shared_ptr& obj) override; + ndk::ScopedAStatus getUltrasonicsArrayList( + std::vector* list) override; + + // Implementation details + EvsEnumerator(const std::shared_ptr< + ::aidl::android::frameworks::automotive::display::ICarDisplayProxy>& + proxyService); + + void notifyDeviceStatusChange(const std::string_view& deviceName, evs::DeviceStatusType type); + + private: + struct CameraRecord { + evs::CameraDesc desc; + std::weak_ptr activeInstance; + + CameraRecord(const char* cameraId) : desc() { desc.id = cameraId; } + }; + + class ActiveDisplays { + public: + struct DisplayInfo { + int32_t id{-1}; + std::weak_ptr displayWeak; + uintptr_t internalDisplayRawAddr; + }; + + std::optional popDisplay(int32_t id); + + std::optional popDisplay(const std::shared_ptr& display); + + std::unordered_map getAllDisplays(); + + bool tryInsert(int32_t id, const std::shared_ptr& display); + + private: + std::mutex mMutex; + std::unordered_map mIdToDisplay GUARDED_BY(mMutex); + std::unordered_map mDisplayToId GUARDED_BY(mMutex); + }; + + bool checkPermission(); + void closeCamera_impl(const std::shared_ptr& pCamera, + const std::string& cameraId); + + static bool qualifyCaptureDevice(const char* deviceName); + static CameraRecord* findCameraById(const std::string& cameraId); + static void enumerateCameras(); + static bool addCaptureDevice(const std::string& deviceName); + static bool removeCaptureDevice(const std::string& deviceName); + // Enumerate available displays and return an id of the internal display + static uint64_t enumerateDisplays(); + + static ActiveDisplays& mutableActiveDisplays(); + + // NOTE: All members values are static so that all clients operate on the same state + // That is to say, this is effectively a singleton despite the fact that HIDL + // constructs a new instance for each client. + // Because our server has a single thread in the thread pool, these values are + // never accessed concurrently despite potentially having multiple instance objects + // using them. + static std::unordered_map sCameraList; + // Object destructs if client dies. + static std::mutex sLock; // Mutex on shared camera device list. + static std::condition_variable sCameraSignal; // Signal on camera device addition. + static std::unique_ptr sConfigManager; // ConfigManager + static std::shared_ptr<::aidl::android::frameworks::automotive::display::ICarDisplayProxy> + sDisplayProxy; + static std::unordered_map sDisplayPortList; + + uint64_t mInternalDisplayId; + std::shared_ptr mCallback; +}; + +} // namespace aidl::android::hardware::automotive::evs::implementation diff --git a/automotive/evs/aidl/impl/default/include/EvsGlDisplay.h b/automotive/evs/aidl/impl/default/include/EvsGlDisplay.h new file mode 100644 index 0000000000..ceabd9e863 --- /dev/null +++ b/automotive/evs/aidl/impl/default/include/EvsGlDisplay.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "GlWrapper.h" + +#include +#include +#include +#include +#include + +#include + +namespace aidl::android::hardware::automotive::evs::implementation { + +class EvsGlDisplay final : public BnEvsDisplay { + public: + // Methods from ::aidl::android::hardware::automotive::evs::IEvsDisplay + // follow. + ndk::ScopedAStatus getDisplayInfo(evs::DisplayDesc* _aidl_return) override; + ndk::ScopedAStatus getDisplayState(evs::DisplayState* _aidl_return) override; + ndk::ScopedAStatus getTargetBuffer(evs::BufferDesc* _aidl_return) override; + ndk::ScopedAStatus returnTargetBufferForDisplay(const evs::BufferDesc& buffer) override; + ndk::ScopedAStatus setDisplayState(evs::DisplayState state) override; + + // Implementation details + EvsGlDisplay(const std::shared_ptr& service, + uint64_t displayId); + virtual ~EvsGlDisplay() override; + + // This gets called if another caller "steals" ownership of the display + void forceShutdown(); + + private: + // A graphics buffer into which we'll store images. This member variable + // will be protected by semaphores. + struct BufferRecord { + ::aidl::android::hardware::graphics::common::HardwareBufferDescription description; + buffer_handle_t handle; + int fingerprint; + } mBuffer; + + // State of a rendering thread + enum RenderThreadStates { + STOPPED = 0, + STOPPING = 1, + RUN = 2, + }; + + uint64_t mDisplayId; + evs::DisplayDesc mInfo; + evs::DisplayState mRequestedState GUARDED_BY(mLock) = evs::DisplayState::NOT_VISIBLE; + std::shared_ptr mDisplayProxy; + + GlWrapper mGlWrapper; + mutable std::mutex mLock; + + // This tells us whether or not our buffer is in use. Protected by + // semaphores. + bool mBufferBusy = false; + + // Variables to synchronize a rendering thread w/ main and binder threads + std::thread mRenderThread; + RenderThreadStates mState GUARDED_BY(mLock) = STOPPED; + bool mBufferReady = false; + void renderFrames(); + bool initializeGlContextLocked() REQUIRES(mLock); + + std::condition_variable mBufferReadyToUse; + std::condition_variable mBufferReadyToRender; + std::condition_variable mBufferDone; +}; + +} // namespace aidl::android::hardware::automotive::evs::implementation diff --git a/automotive/evs/aidl/impl/default/include/EvsMockCamera.h b/automotive/evs/aidl/impl/default/include/EvsMockCamera.h new file mode 100644 index 0000000000..46d47e77a2 --- /dev/null +++ b/automotive/evs/aidl/impl/default/include/EvsMockCamera.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "ConfigManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include +#include + +#include +#include + +namespace aidl::android::hardware::automotive::evs::implementation { + +class EvsMockCamera : public evs::BnEvsCamera { + // This prevents constructors from direct access while it allows this class to + // be instantiated via ndk::SharedRefBase::make<>. + private: + struct Sigil { + explicit Sigil() = default; + }; + + public: + // Methods from ::android::hardware::automotive::evs::IEvsCamera follow. + ndk::ScopedAStatus doneWithFrame(const std::vector& buffers) override; + ndk::ScopedAStatus forcePrimaryClient( + const std::shared_ptr& display) override; + ndk::ScopedAStatus getCameraInfo(evs::CameraDesc* _aidl_return) override; + ndk::ScopedAStatus getExtendedInfo(int32_t opaqueIdentifier, + std::vector* value) override; + ndk::ScopedAStatus getIntParameter(evs::CameraParam id, std::vector* value) override; + ndk::ScopedAStatus getIntParameterRange(evs::CameraParam id, + evs::ParameterRange* _aidl_return) override; + ndk::ScopedAStatus getParameterList(std::vector* _aidl_return) override; + ndk::ScopedAStatus getPhysicalCameraInfo(const std::string& deviceId, + evs::CameraDesc* _aidl_return) override; + ndk::ScopedAStatus importExternalBuffers(const std::vector& buffers, + int32_t* _aidl_return) override; + ndk::ScopedAStatus pauseVideoStream() override; + ndk::ScopedAStatus resumeVideoStream() override; + ndk::ScopedAStatus setExtendedInfo(int32_t opaqueIdentifier, + const std::vector& opaqueValue) override; + ndk::ScopedAStatus setIntParameter(evs::CameraParam id, int32_t value, + std::vector* effectiveValue) override; + ndk::ScopedAStatus setPrimaryClient() override; + ndk::ScopedAStatus setMaxFramesInFlight(int32_t bufferCount) override; + ndk::ScopedAStatus startVideoStream( + const std::shared_ptr& receiver) override; + ndk::ScopedAStatus stopVideoStream() override; + ndk::ScopedAStatus unsetPrimaryClient() override; + + static std::shared_ptr Create(const char* deviceName); + static std::shared_ptr Create( + const char* deviceName, std::unique_ptr& camInfo, + const evs::Stream* streamCfg = nullptr); + EvsMockCamera(const EvsMockCamera&) = delete; + EvsMockCamera& operator=(const EvsMockCamera&) = delete; + + virtual ~EvsMockCamera() override; + void shutdown(); + + const evs::CameraDesc& getDesc() { return mDescription; } + + // Constructors + EvsMockCamera(Sigil sigil, const char* deviceName, + std::unique_ptr& camInfo); + + private: + // These three functions are expected to be called while mAccessLock is held + bool setAvailableFrames_Locked(unsigned bufferCount); + unsigned increaseAvailableFrames_Locked(unsigned numToAdd); + unsigned decreaseAvailableFrames_Locked(unsigned numToRemove); + + void generateFrames(); + void fillMockFrame(buffer_handle_t handle, const AHardwareBuffer_Desc* pDesc); + void returnBufferLocked(const uint32_t bufferId); + ndk::ScopedAStatus stopVideoStream_impl(); + + CameraDesc mDescription = {}; // The properties of this camera + + std::thread mCaptureThread; // The thread we'll use to synthesize frames + + // The callback used to deliver each frame + std::shared_ptr mStream; + + // Horizontal pixel count in the buffers + uint32_t mWidth = 0; + // Vertical pixel count in the buffers + uint32_t mHeight = 0; + // Values from android_pixel_format_t + uint32_t mFormat = HAL_PIXEL_FORMAT_RGBA_8888; + // Values from from Gralloc.h + uint64_t mUsage = + GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_OFTEN; + // Bytes per line in the buffers + uint32_t mStride = 0; + + struct BufferRecord { + buffer_handle_t handle; + bool inUse; + + explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false){}; + }; + + std::vector mBuffers; // Graphics buffers to transfer images + unsigned mFramesAllowed; // How many buffers are we currently using + unsigned mFramesInUse; // How many buffers are currently outstanding + + enum StreamStateValues { + STOPPED, + RUNNING, + STOPPING, + DEAD, + }; + StreamStateValues mStreamState; + + // Synchronization necessary to deconflict mCaptureThread from the main service thread + std::mutex mAccessLock; + + // Static camera module information + std::unique_ptr& mCameraInfo; + + // For the extended info + std::unordered_map> mExtInfo; + std::unordered_map mParams; +}; + +} // namespace aidl::android::hardware::automotive::evs::implementation diff --git a/automotive/evs/aidl/impl/default/include/GlWrapper.h b/automotive/evs/aidl/impl/default/include/GlWrapper.h new file mode 100644 index 0000000000..adb250c8e1 --- /dev/null +++ b/automotive/evs/aidl/impl/default/include/GlWrapper.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace aidl::android::hardware::automotive::evs::implementation { + +namespace automotivedisplay = ::aidl::android::frameworks::automotive::display; + +class GlWrapper { + public: + GlWrapper() : mSurfaceHolder(::android::SurfaceHolderUniquePtr(nullptr, nullptr)) {} + bool initialize(const std::shared_ptr& svc, + uint64_t displayId); + void shutdown(); + + bool updateImageTexture( + buffer_handle_t handle, + const ::aidl::android::hardware::graphics::common::HardwareBufferDescription& + description); + void renderImageToScreen(); + + void showWindow(const std::shared_ptr& svc, + uint64_t displayId); + void hideWindow(const std::shared_ptr& svc, + uint64_t displayId); + + unsigned getWidth() { return mWidth; }; + unsigned getHeight() { return mHeight; }; + + private: + ::android::sp<::android::hardware::graphics::bufferqueue::V2_0::IGraphicBufferProducer> + mGfxBufferProducer; + + EGLDisplay mDisplay; + EGLSurface mSurface; + EGLContext mContext; + + unsigned mWidth = 0; + unsigned mHeight = 0; + + EGLImageKHR mKHRimage = EGL_NO_IMAGE_KHR; + + GLuint mTextureMap = 0; + GLuint mShaderProgram = 0; + + // Opaque handle for a native hardware buffer defined in + // frameworks/native/opengl/include/EGL/eglplatform.h + ANativeWindow* mWindow; + + // Pointer to a Surface wrapper. + ::android::SurfaceHolderUniquePtr mSurfaceHolder; +}; + +} // namespace aidl::android::hardware::automotive::evs::implementation diff --git a/automotive/evs/aidl/impl/default/manifest_evs-default-service.xml b/automotive/evs/aidl/impl/default/manifest_evs-default-service.xml new file mode 100644 index 0000000000..8480651e77 --- /dev/null +++ b/automotive/evs/aidl/impl/default/manifest_evs-default-service.xml @@ -0,0 +1,6 @@ + + + android.hardware.automotive.evs + IEvsEnumerator/hw/0 + + diff --git a/automotive/evs/aidl/impl/default/resources/evs_mock_configuration.xml b/automotive/evs/aidl/impl/default/resources/evs_mock_configuration.xml new file mode 100644 index 0000000000..6cbc18eacf --- /dev/null +++ b/automotive/evs/aidl/impl/default/resources/evs_mock_configuration.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/automotive/evs/aidl/impl/default/src/ConfigManager.cpp b/automotive/evs/aidl/impl/default/src/ConfigManager.cpp new file mode 100644 index 0000000000..da791ed0b9 --- /dev/null +++ b/automotive/evs/aidl/impl/default/src/ConfigManager.cpp @@ -0,0 +1,992 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ConfigManager.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +using ::aidl::android::hardware::automotive::evs::CameraParam; +using ::aidl::android::hardware::graphics::common::PixelFormat; +using ::tinyxml2::XMLAttribute; +using ::tinyxml2::XMLDocument; +using ::tinyxml2::XMLElement; + +} // namespace + +std::string_view ConfigManager::sConfigDefaultPath = + "/vendor/etc/automotive/evs/evs_mock_hal_configuration.xml"; +std::string_view ConfigManager::sConfigOverridePath = + "/vendor/etc/automotive/evs/evs_configuration_override.xml"; + +void ConfigManager::printElementNames(const XMLElement* rootElem, const std::string& prefix) const { + const XMLElement* curElem = rootElem; + + while (curElem != nullptr) { + LOG(VERBOSE) << "[ELEM] " << prefix << curElem->Name(); + const XMLAttribute* curAttr = curElem->FirstAttribute(); + while (curAttr) { + LOG(VERBOSE) << "[ATTR] " << prefix << curAttr->Name() << ": " << curAttr->Value(); + curAttr = curAttr->Next(); + } + + /* recursively go down to descendants */ + printElementNames(curElem->FirstChildElement(), prefix + "\t"); + + curElem = curElem->NextSiblingElement(); + } +} + +void ConfigManager::readCameraInfo(const XMLElement* const aCameraElem) { + if (aCameraElem == nullptr) { + LOG(WARNING) << "XML file does not have required camera element"; + return; + } + + const XMLElement* curElem = aCameraElem->FirstChildElement(); + while (curElem != nullptr) { + if (!strcmp(curElem->Name(), "group")) { + /* camera group identifier */ + const char* id = curElem->FindAttribute("id")->Value(); + + /* create a camera group to be filled */ + CameraGroupInfo* aCamera = new CameraGroupInfo(); + + /* read camera device information */ + if (!readCameraDeviceInfo(aCamera, curElem)) { + LOG(WARNING) << "Failed to read a camera information of " << id; + delete aCamera; + continue; + } + + /* camera group synchronization */ + const char* sync = curElem->FindAttribute("synchronized")->Value(); + if (!strcmp(sync, "CALIBRATED")) { + aCamera->synchronized = ANDROID_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED; + } else if (!strcmp(sync, "APPROXIMATE")) { + aCamera->synchronized = ANDROID_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE; + } else { + aCamera->synchronized = 0; // Not synchronized + } + + /* add a group to hash map */ + mCameraGroups.insert_or_assign(id, std::unique_ptr(aCamera)); + } else if (!std::strcmp(curElem->Name(), "device")) { + /* camera unique identifier */ + const char* id = curElem->FindAttribute("id")->Value(); + + /* camera mount location */ + const char* pos = curElem->FindAttribute("position")->Value(); + + /* create a camera device to be filled */ + CameraInfo* aCamera = new CameraInfo(); + + /* read camera device information */ + if (!readCameraDeviceInfo(aCamera, curElem)) { + LOG(WARNING) << "Failed to read a camera information of " << id; + delete aCamera; + continue; + } + + /* store read camera module information */ + mCameraInfo.insert_or_assign(id, std::unique_ptr(aCamera)); + + /* assign a camera device to a position group */ + mCameraPosition[pos].insert(id); + } else { + /* ignore other device types */ + LOG(DEBUG) << "Unknown element " << curElem->Name() << " is ignored"; + } + + curElem = curElem->NextSiblingElement(); + } +} + +bool ConfigManager::readCameraDeviceInfo(CameraInfo* aCamera, const XMLElement* aDeviceElem) { + if (aCamera == nullptr || aDeviceElem == nullptr) { + return false; + } + + /* size information to allocate camera_metadata_t */ + size_t totalEntries = 0; + size_t totalDataSize = 0; + + /* read device capabilities */ + totalEntries += + readCameraCapabilities(aDeviceElem->FirstChildElement("caps"), aCamera, totalDataSize); + + /* read camera metadata */ + totalEntries += readCameraMetadata(aDeviceElem->FirstChildElement("characteristics"), aCamera, + totalDataSize); + + /* construct camera_metadata_t */ + if (!constructCameraMetadata(aCamera, totalEntries, totalDataSize)) { + LOG(WARNING) << "Either failed to allocate memory or " + << "allocated memory was not large enough"; + } + + return true; +} + +size_t ConfigManager::readCameraCapabilities(const XMLElement* const aCapElem, CameraInfo* aCamera, + size_t& dataSize) { + if (aCapElem == nullptr || aCamera == nullptr) { + return 0; + } + + std::string token; + const XMLElement* curElem = nullptr; + + /* a list of supported camera parameters/controls */ + curElem = aCapElem->FirstChildElement("supported_controls"); + if (curElem != nullptr) { + const XMLElement* ctrlElem = curElem->FirstChildElement("control"); + while (ctrlElem != nullptr) { + const char* nameAttr = ctrlElem->FindAttribute("name")->Value(); + int32_t minVal = INT32_MIN, maxVal = INT32_MAX; + if (!android::base::ParseInt(ctrlElem->FindAttribute("min")->Value(), &minVal)) { + LOG(WARNING) << "Failed to parse " << ctrlElem->FindAttribute("min")->Value(); + } + + if (!android::base::ParseInt(ctrlElem->FindAttribute("max")->Value(), &maxVal)) { + LOG(WARNING) << "Failed to parse " << ctrlElem->FindAttribute("max")->Value(); + } + + int32_t stepVal = 1; + const XMLAttribute* stepAttr = ctrlElem->FindAttribute("step"); + if (stepAttr != nullptr) { + if (!android::base::ParseInt(stepAttr->Value(), &stepVal)) { + LOG(WARNING) << "Failed to parse " << stepAttr->Value(); + } + } + + CameraParam aParam; + if (ConfigManagerUtil::convertToEvsCameraParam(nameAttr, aParam)) { + aCamera->controls.insert_or_assign( + aParam, std::move(std::make_tuple(minVal, maxVal, stepVal))); + } + + ctrlElem = ctrlElem->NextSiblingElement("control"); + } + } + + /* a list of camera stream configurations */ + curElem = aCapElem->FirstChildElement("stream"); + while (curElem != nullptr) { + /* read 5 attributes */ + const XMLAttribute* idAttr = curElem->FindAttribute("id"); + const XMLAttribute* widthAttr = curElem->FindAttribute("width"); + const XMLAttribute* heightAttr = curElem->FindAttribute("height"); + const XMLAttribute* fmtAttr = curElem->FindAttribute("format"); + const XMLAttribute* fpsAttr = curElem->FindAttribute("framerate"); + + int32_t id = -1; + int32_t framerate = 0; + if (!android::base::ParseInt(idAttr->Value(), &id)) { + LOG(WARNING) << "Failed to parse " << idAttr->Value(); + } + if (fpsAttr != nullptr) { + if (!android::base::ParseInt(fpsAttr->Value(), &framerate)) { + LOG(WARNING) << "Failed to parse " << fpsAttr->Value(); + } + } + + PixelFormat format = PixelFormat::UNSPECIFIED; + if (ConfigManagerUtil::convertToPixelFormat(fmtAttr->Value(), format)) { + StreamConfiguration cfg = { + .id = id, + .format = format, + .type = ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT, + .framerate = framerate, + }; + + if (!android::base::ParseInt(widthAttr->Value(), &cfg.width) || + !android::base::ParseInt(heightAttr->Value(), &cfg.height)) { + LOG(WARNING) << "Failed to parse " << widthAttr->Value() << " and " + << heightAttr->Value(); + } + aCamera->streamConfigurations.insert_or_assign(id, cfg); + } + + curElem = curElem->NextSiblingElement("stream"); + } + + dataSize = calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS), + aCamera->streamConfigurations.size() * sizeof(StreamConfiguration)); + + /* a single camera metadata entry contains multiple stream configurations */ + return dataSize > 0 ? 1 : 0; +} + +size_t ConfigManager::readCameraMetadata(const XMLElement* const aParamElem, CameraInfo* aCamera, + size_t& dataSize) { + if (aParamElem == nullptr || aCamera == nullptr) { + return 0; + } + + const XMLElement* curElem = aParamElem->FirstChildElement("parameter"); + size_t numEntries = 0; + camera_metadata_tag_t tag; + while (curElem != nullptr) { + if (ConfigManagerUtil::convertToMetadataTag(curElem->FindAttribute("name")->Value(), tag)) { + switch (tag) { + case ANDROID_LENS_DISTORTION: + case ANDROID_LENS_POSE_ROTATION: + case ANDROID_LENS_POSE_TRANSLATION: + case ANDROID_LENS_INTRINSIC_CALIBRATION: { + /* float[] */ + size_t count = 0; + void* data = ConfigManagerUtil::convertFloatArray( + curElem->FindAttribute("size")->Value(), + curElem->FindAttribute("value")->Value(), count); + + aCamera->cameraMetadata.insert_or_assign(tag, std::make_pair(data, count)); + + ++numEntries; + dataSize += calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type(tag), count); + + break; + } + + case ANDROID_REQUEST_AVAILABLE_CAPABILITIES: { + camera_metadata_enum_android_request_available_capabilities_t* data = + new camera_metadata_enum_android_request_available_capabilities_t[1]; + if (ConfigManagerUtil::convertToCameraCapability( + curElem->FindAttribute("value")->Value(), *data)) { + aCamera->cameraMetadata.insert_or_assign(tag, + std::make_pair((void*)data, 1)); + + ++numEntries; + dataSize += calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type(tag), 1); + } + break; + } + + case ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS: { + /* a comma-separated list of physical camera devices */ + size_t len = strlen(curElem->FindAttribute("value")->Value()); + char* data = new char[len + 1]; + memcpy(data, curElem->FindAttribute("value")->Value(), len * sizeof(char)); + + /* replace commas with null char */ + char* p = data; + while (*p != '\0') { + if (*p == ',') { + *p = '\0'; + } + ++p; + } + + aCamera->cameraMetadata.insert_or_assign(tag, + std::make_pair((void*)data, len + 1)); + + ++numEntries; + dataSize += calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type(tag), len); + break; + } + + /* TODO(b/140416878): add vendor-defined/custom tag support */ + default: + LOG(WARNING) << "Parameter " << curElem->FindAttribute("name")->Value() + << " is not supported"; + break; + } + } else { + LOG(WARNING) << "Unsupported metadata tag " << curElem->FindAttribute("name")->Value() + << " is found."; + } + + curElem = curElem->NextSiblingElement("parameter"); + } + + return numEntries; +} + +bool ConfigManager::constructCameraMetadata(CameraInfo* aCamera, size_t totalEntries, + size_t totalDataSize) { + if (aCamera == nullptr || !aCamera->allocate(totalEntries, totalDataSize)) { + LOG(ERROR) << "Failed to allocate memory for camera metadata"; + return false; + } + + const size_t numStreamConfigs = aCamera->streamConfigurations.size(); + std::unique_ptr data(new int32_t[sizeof(StreamConfiguration) * numStreamConfigs]); + int32_t* ptr = data.get(); + for (auto& cfg : aCamera->streamConfigurations) { + memcpy(ptr, &cfg.second, sizeof(StreamConfiguration)); + ptr += sizeof(StreamConfiguration); + } + int32_t err = add_camera_metadata_entry( + aCamera->characteristics, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, data.get(), + numStreamConfigs * sizeof(StreamConfiguration)); + + if (err) { + LOG(ERROR) << "Failed to add stream configurations to metadata, ignored"; + return false; + } + + bool success = true; + for (auto& [tag, entry] : aCamera->cameraMetadata) { + /* try to add new camera metadata entry */ + int32_t err = + add_camera_metadata_entry(aCamera->characteristics, tag, entry.first, entry.second); + if (err) { + LOG(ERROR) << "Failed to add an entry with a tag, " << std::hex << tag; + + /* may exceed preallocated capacity */ + LOG(ERROR) << "Camera metadata has " + << get_camera_metadata_entry_count(aCamera->characteristics) << " / " + << get_camera_metadata_entry_capacity(aCamera->characteristics) + << " entries and " + << get_camera_metadata_data_count(aCamera->characteristics) << " / " + << get_camera_metadata_data_capacity(aCamera->characteristics) + << " bytes are filled."; + LOG(ERROR) << "\tCurrent metadata entry requires " + << calculate_camera_metadata_entry_data_size(tag, entry.second) << " bytes."; + + success = false; + } + } + + LOG(VERBOSE) << "Camera metadata has " + << get_camera_metadata_entry_count(aCamera->characteristics) << " / " + << get_camera_metadata_entry_capacity(aCamera->characteristics) << " entries and " + << get_camera_metadata_data_count(aCamera->characteristics) << " / " + << get_camera_metadata_data_capacity(aCamera->characteristics) + << " bytes are filled."; + return success; +} + +void ConfigManager::readSystemInfo(const XMLElement* const aSysElem) { + if (aSysElem == nullptr) { + return; + } + + /* + * Please note that this function assumes that a given system XML element + * and its child elements follow DTD. If it does not, it will cause a + * segmentation fault due to the failure of finding expected attributes. + */ + + /* read number of cameras available in the system */ + const XMLElement* xmlElem = aSysElem->FirstChildElement("num_cameras"); + if (xmlElem != nullptr) { + if (!android::base::ParseInt(xmlElem->FindAttribute("value")->Value(), + &mSystemInfo.numCameras)) { + LOG(WARNING) << "Failed to parse " << xmlElem->FindAttribute("value")->Value(); + } + } +} + +void ConfigManager::readDisplayInfo(const XMLElement* const aDisplayElem) { + if (aDisplayElem == nullptr) { + LOG(WARNING) << "XML file does not have required camera element"; + return; + } + + const XMLElement* curDev = aDisplayElem->FirstChildElement("device"); + while (curDev != nullptr) { + const char* id = curDev->FindAttribute("id")->Value(); + std::unique_ptr dpy(new DisplayInfo()); + if (dpy == nullptr) { + LOG(ERROR) << "Failed to allocate memory for DisplayInfo"; + return; + } + + const XMLElement* cap = curDev->FirstChildElement("caps"); + if (cap != nullptr) { + const XMLElement* curStream = cap->FirstChildElement("stream"); + while (curStream != nullptr) { + /* read 4 attributes */ + const XMLAttribute* idAttr = curStream->FindAttribute("id"); + const XMLAttribute* widthAttr = curStream->FindAttribute("width"); + const XMLAttribute* heightAttr = curStream->FindAttribute("height"); + const XMLAttribute* fmtAttr = curStream->FindAttribute("format"); + + int32_t id = -1; + if (!android::base::ParseInt(idAttr->Value(), &id)) { + LOG(WARNING) << "Failed to parse " << idAttr->Value(); + } + PixelFormat format = PixelFormat::UNSPECIFIED; + if (ConfigManagerUtil::convertToPixelFormat(fmtAttr->Value(), format)) { + StreamConfiguration cfg = { + .id = id, + .format = format, + .type = ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT, + }; + if (!android::base::ParseInt(widthAttr->Value(), &cfg.width) || + !android::base::ParseInt(heightAttr->Value(), &cfg.height)) { + LOG(WARNING) << "Failed to parse " << widthAttr->Value() << " and " + << heightAttr->Value(); + } + dpy->streamConfigurations.insert_or_assign(id, cfg); + } + + curStream = curStream->NextSiblingElement("stream"); + } + } + + mDisplayInfo.insert_or_assign(id, std::move(dpy)); + curDev = curDev->NextSiblingElement("device"); + } + + return; +} + +bool ConfigManager::readConfigDataFromXML() noexcept { + XMLDocument xmlDoc; + + const int64_t parsingStart = android::elapsedRealtimeNano(); + + /* load and parse a configuration file */ + xmlDoc.LoadFile(sConfigOverridePath.data()); + if (xmlDoc.ErrorID() != tinyxml2::XML_SUCCESS) { + xmlDoc.LoadFile(sConfigDefaultPath.data()); + if (xmlDoc.ErrorID() != tinyxml2::XML_SUCCESS) { + LOG(ERROR) << "Failed to load and/or parse a configuration file, " << xmlDoc.ErrorStr(); + return false; + } + } + + /* retrieve the root element */ + const XMLElement* rootElem = xmlDoc.RootElement(); + if (std::strcmp(rootElem->Name(), "configuration") != 0) { + LOG(ERROR) << "A configuration file is not in the required format. " + << "See /etc/automotive/evs/evs_configuration.dtd"; + return false; + } + + std::unique_lock lock(mConfigLock); + + /* + * parse camera information; this needs to be done before reading system + * information + */ + readCameraInfo(rootElem->FirstChildElement("camera")); + + /* parse system information */ + readSystemInfo(rootElem->FirstChildElement("system")); + + /* parse display information */ + readDisplayInfo(rootElem->FirstChildElement("display")); + + /* configuration data is ready to be consumed */ + mIsReady = true; + + /* notify that configuration data is ready */ + lock.unlock(); + mConfigCond.notify_all(); + + const int64_t parsingEnd = android::elapsedRealtimeNano(); + LOG(INFO) << "Parsing configuration file takes " << std::scientific + << (double)(parsingEnd - parsingStart) / 1000000.0 << " ms."; + + return true; +} + +bool ConfigManager::readConfigDataFromBinary() { + /* Temporary buffer to hold configuration data read from a binary file */ + char mBuffer[1024]; + + std::fstream srcFile; + const int64_t readStart = android::elapsedRealtimeNano(); + + srcFile.open(mBinaryFilePath, std::fstream::in | std::fstream::binary); + if (!srcFile) { + LOG(ERROR) << "Failed to open a source binary file, " << mBinaryFilePath; + return false; + } + + std::unique_lock lock(mConfigLock); + mIsReady = false; + + /* read configuration data into the internal buffer */ + srcFile.read(mBuffer, sizeof(mBuffer)); + LOG(VERBOSE) << __FUNCTION__ << ": " << srcFile.gcount() << " bytes are read."; + char* p = mBuffer; + size_t sz = 0; + + /* read number of camera group information entries */ + const size_t ngrps = *(reinterpret_cast(p)); + p += sizeof(size_t); + + /* read each camera information entry */ + for (size_t cidx = 0; cidx < ngrps; ++cidx) { + /* read camera identifier */ + std::string cameraId = *(reinterpret_cast(p)); + p += sizeof(std::string); + + /* size of camera_metadata_t */ + const size_t num_entry = *(reinterpret_cast(p)); + p += sizeof(size_t); + const size_t num_data = *(reinterpret_cast(p)); + p += sizeof(size_t); + + /* create CameraInfo and add it to hash map */ + std::unique_ptr aCamera; + if (aCamera == nullptr || !aCamera->allocate(num_entry, num_data)) { + LOG(ERROR) << "Failed to create new CameraInfo object"; + mCameraInfo.clear(); + return false; + } + + /* controls */ + typedef struct { + CameraParam cid; + int32_t min; + int32_t max; + int32_t step; + } CameraCtrl; + sz = *(reinterpret_cast(p)); + p += sizeof(size_t); + CameraCtrl* ptr = reinterpret_cast(p); + for (size_t idx = 0; idx < sz; ++idx) { + CameraCtrl temp = *ptr++; + aCamera->controls.insert_or_assign( + temp.cid, std::move(std::make_tuple(temp.min, temp.max, temp.step))); + } + p = reinterpret_cast(ptr); + + /* stream configurations */ + sz = *(reinterpret_cast(p)); + p += sizeof(size_t); + int32_t* i32_ptr = reinterpret_cast(p); + for (size_t idx = 0; idx < sz; ++idx) { + const int32_t id = *i32_ptr++; + + StreamConfiguration temp; + memcpy(&temp, i32_ptr, sizeof(StreamConfiguration)); + i32_ptr += sizeof(StreamConfiguration); + aCamera->streamConfigurations.insert_or_assign(id, temp); + } + p = reinterpret_cast(i32_ptr); + + /* synchronization */ + aCamera->synchronized = *(reinterpret_cast(p)); + p += sizeof(int32_t); + + for (size_t idx = 0; idx < num_entry; ++idx) { + /* Read camera metadata entries */ + camera_metadata_tag_t tag = *reinterpret_cast(p); + p += sizeof(camera_metadata_tag_t); + const size_t count = *reinterpret_cast(p); + p += sizeof(size_t); + + const int32_t type = get_camera_metadata_tag_type(tag); + switch (type) { + case TYPE_BYTE: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(uint8_t); + break; + } + case TYPE_INT32: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(int32_t); + break; + } + case TYPE_FLOAT: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(float); + break; + } + case TYPE_INT64: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(int64_t); + break; + } + case TYPE_DOUBLE: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(double); + break; + } + case TYPE_RATIONAL: + p += count * sizeof(camera_metadata_rational_t); + break; + default: + LOG(WARNING) << "Type " << type << " is unknown; " + << "data may be corrupted."; + break; + } + } + + mCameraInfo.insert_or_assign(cameraId, std::move(aCamera)); + } + + /* read number of camera information entries */ + const size_t ncams = *(reinterpret_cast(p)); + p += sizeof(size_t); + + /* read each camera information entry */ + for (size_t cidx = 0; cidx < ncams; ++cidx) { + /* read camera identifier */ + std::string cameraId = *(reinterpret_cast(p)); + p += sizeof(std::string); + + /* size of camera_metadata_t */ + const size_t num_entry = *(reinterpret_cast(p)); + p += sizeof(size_t); + const size_t num_data = *(reinterpret_cast(p)); + p += sizeof(size_t); + + /* create CameraInfo and add it to hash map */ + std::unique_ptr aCamera; + if (aCamera == nullptr || !aCamera->allocate(num_entry, num_data)) { + LOG(ERROR) << "Failed to create new CameraInfo object"; + mCameraInfo.clear(); + return false; + } + + /* controls */ + typedef struct { + CameraParam cid; + int32_t min; + int32_t max; + int32_t step; + } CameraCtrl; + sz = *(reinterpret_cast(p)); + p += sizeof(size_t); + CameraCtrl* ptr = reinterpret_cast(p); + for (size_t idx = 0; idx < sz; ++idx) { + CameraCtrl temp = *ptr++; + aCamera->controls.insert_or_assign( + temp.cid, std::move(std::make_tuple(temp.min, temp.max, temp.step))); + } + p = reinterpret_cast(ptr); + + /* stream configurations */ + sz = *(reinterpret_cast(p)); + p += sizeof(size_t); + int32_t* i32_ptr = reinterpret_cast(p); + for (size_t idx = 0; idx < sz; ++idx) { + const int32_t id = *i32_ptr++; + + StreamConfiguration temp; + memcpy(&temp, i32_ptr, sizeof(StreamConfiguration)); + i32_ptr += sizeof(StreamConfiguration); + aCamera->streamConfigurations.insert_or_assign(id, temp); + } + p = reinterpret_cast(i32_ptr); + + for (size_t idx = 0; idx < num_entry; ++idx) { + /* Read camera metadata entries */ + camera_metadata_tag_t tag = *reinterpret_cast(p); + p += sizeof(camera_metadata_tag_t); + const size_t count = *reinterpret_cast(p); + p += sizeof(size_t); + + const int32_t type = get_camera_metadata_tag_type(tag); + switch (type) { + case TYPE_BYTE: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(uint8_t); + break; + } + case TYPE_INT32: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(int32_t); + break; + } + case TYPE_FLOAT: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(float); + break; + } + case TYPE_INT64: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(int64_t); + break; + } + case TYPE_DOUBLE: { + add_camera_metadata_entry(aCamera->characteristics, tag, p, count); + p += count * sizeof(double); + break; + } + case TYPE_RATIONAL: + p += count * sizeof(camera_metadata_rational_t); + break; + default: + LOG(WARNING) << "Type " << type << " is unknown; " + << "data may be corrupted."; + break; + } + } + + mCameraInfo.insert_or_assign(cameraId, std::move(aCamera)); + } + + mIsReady = true; + + /* notify that configuration data is ready */ + lock.unlock(); + mConfigCond.notify_all(); + + int64_t readEnd = android::elapsedRealtimeNano(); + LOG(INFO) << __FUNCTION__ << " takes " << std::scientific + << (double)(readEnd - readStart) / 1000000.0 << " ms."; + + return true; +} + +bool ConfigManager::writeConfigDataToBinary() { + std::fstream outFile; + + const int64_t writeStart = android::elapsedRealtimeNano(); + + outFile.open(mBinaryFilePath, std::fstream::out | std::fstream::binary); + if (!outFile) { + LOG(ERROR) << "Failed to open a destination binary file, " << mBinaryFilePath; + return false; + } + + /* lock a configuration data while it's being written to the filesystem */ + std::lock_guard lock(mConfigLock); + + /* write camera group information */ + size_t sz = mCameraGroups.size(); + outFile.write(reinterpret_cast(&sz), sizeof(size_t)); + for (auto&& [camId, camInfo] : mCameraGroups) { + LOG(INFO) << "Storing camera group " << camId; + + /* write a camera identifier string */ + outFile.write(reinterpret_cast(&camId), sizeof(std::string)); + + /* controls */ + sz = camInfo->controls.size(); + outFile.write(reinterpret_cast(&sz), sizeof(size_t)); + for (auto&& [ctrl, range] : camInfo->controls) { + outFile.write(reinterpret_cast(&ctrl), sizeof(CameraParam)); + outFile.write(reinterpret_cast(&std::get<0>(range)), sizeof(int32_t)); + outFile.write(reinterpret_cast(&std::get<1>(range)), sizeof(int32_t)); + outFile.write(reinterpret_cast(&std::get<2>(range)), sizeof(int32_t)); + } + + /* stream configurations */ + sz = camInfo->streamConfigurations.size(); + outFile.write(reinterpret_cast(&sz), sizeof(size_t)); + for (auto&& [sid, cfg] : camInfo->streamConfigurations) { + outFile.write(reinterpret_cast(sid), sizeof(int32_t)); + outFile.write(reinterpret_cast(&cfg), sizeof(cfg)); + } + + /* synchronization */ + outFile.write(reinterpret_cast(&camInfo->synchronized), sizeof(int32_t)); + + /* size of camera_metadata_t */ + size_t num_entry = 0; + size_t num_data = 0; + if (camInfo->characteristics != nullptr) { + num_entry = get_camera_metadata_entry_count(camInfo->characteristics); + num_data = get_camera_metadata_data_count(camInfo->characteristics); + } + outFile.write(reinterpret_cast(&num_entry), sizeof(size_t)); + outFile.write(reinterpret_cast(&num_data), sizeof(size_t)); + + /* write each camera metadata entry */ + if (num_entry > 0) { + camera_metadata_entry_t entry; + for (size_t idx = 0; idx < num_entry; ++idx) { + if (get_camera_metadata_entry(camInfo->characteristics, idx, &entry)) { + LOG(ERROR) << "Failed to retrieve camera metadata entry " << idx; + outFile.close(); + return false; + } + + outFile.write(reinterpret_cast(&entry.tag), sizeof(entry.tag)); + outFile.write(reinterpret_cast(&entry.count), sizeof(entry.count)); + + int32_t type = get_camera_metadata_tag_type(entry.tag); + switch (type) { + case TYPE_BYTE: + outFile.write(reinterpret_cast(entry.data.u8), + sizeof(uint8_t) * entry.count); + break; + case TYPE_INT32: + outFile.write(reinterpret_cast(entry.data.i32), + sizeof(int32_t) * entry.count); + break; + case TYPE_FLOAT: + outFile.write(reinterpret_cast(entry.data.f), + sizeof(float) * entry.count); + break; + case TYPE_INT64: + outFile.write(reinterpret_cast(entry.data.i64), + sizeof(int64_t) * entry.count); + break; + case TYPE_DOUBLE: + outFile.write(reinterpret_cast(entry.data.d), + sizeof(double) * entry.count); + break; + case TYPE_RATIONAL: + [[fallthrough]]; + default: + LOG(WARNING) << "Type " << type << " is not supported."; + break; + } + } + } + } + + /* write camera device information */ + sz = mCameraInfo.size(); + outFile.write(reinterpret_cast(&sz), sizeof(size_t)); + for (auto&& [camId, camInfo] : mCameraInfo) { + LOG(INFO) << "Storing camera " << camId; + + /* write a camera identifier string */ + outFile.write(reinterpret_cast(&camId), sizeof(std::string)); + + /* controls */ + sz = camInfo->controls.size(); + outFile.write(reinterpret_cast(&sz), sizeof(size_t)); + for (auto& [ctrl, range] : camInfo->controls) { + outFile.write(reinterpret_cast(&ctrl), sizeof(CameraParam)); + outFile.write(reinterpret_cast(&std::get<0>(range)), sizeof(int32_t)); + outFile.write(reinterpret_cast(&std::get<1>(range)), sizeof(int32_t)); + outFile.write(reinterpret_cast(&std::get<2>(range)), sizeof(int32_t)); + } + + /* stream configurations */ + sz = camInfo->streamConfigurations.size(); + outFile.write(reinterpret_cast(&sz), sizeof(size_t)); + for (auto&& [sid, cfg] : camInfo->streamConfigurations) { + outFile.write(reinterpret_cast(sid), sizeof(int32_t)); + outFile.write(reinterpret_cast(&cfg), sizeof(cfg)); + } + + /* size of camera_metadata_t */ + size_t num_entry = 0; + size_t num_data = 0; + if (camInfo->characteristics != nullptr) { + num_entry = get_camera_metadata_entry_count(camInfo->characteristics); + num_data = get_camera_metadata_data_count(camInfo->characteristics); + } + outFile.write(reinterpret_cast(&num_entry), sizeof(size_t)); + outFile.write(reinterpret_cast(&num_data), sizeof(size_t)); + + /* write each camera metadata entry */ + if (num_entry > 0) { + camera_metadata_entry_t entry; + for (size_t idx = 0; idx < num_entry; ++idx) { + if (get_camera_metadata_entry(camInfo->characteristics, idx, &entry)) { + LOG(ERROR) << "Failed to retrieve camera metadata entry " << idx; + outFile.close(); + return false; + } + + outFile.write(reinterpret_cast(&entry.tag), sizeof(entry.tag)); + outFile.write(reinterpret_cast(&entry.count), sizeof(entry.count)); + + int32_t type = get_camera_metadata_tag_type(entry.tag); + switch (type) { + case TYPE_BYTE: + outFile.write(reinterpret_cast(entry.data.u8), + sizeof(uint8_t) * entry.count); + break; + case TYPE_INT32: + outFile.write(reinterpret_cast(entry.data.i32), + sizeof(int32_t) * entry.count); + break; + case TYPE_FLOAT: + outFile.write(reinterpret_cast(entry.data.f), + sizeof(float) * entry.count); + break; + case TYPE_INT64: + outFile.write(reinterpret_cast(entry.data.i64), + sizeof(int64_t) * entry.count); + break; + case TYPE_DOUBLE: + outFile.write(reinterpret_cast(entry.data.d), + sizeof(double) * entry.count); + break; + case TYPE_RATIONAL: + [[fallthrough]]; + default: + LOG(WARNING) << "Type " << type << " is not supported."; + break; + } + } + } + } + + outFile.close(); + int64_t writeEnd = android::elapsedRealtimeNano(); + LOG(INFO) << __FUNCTION__ << " takes " << std::scientific + << (double)(writeEnd - writeStart) / 1000000.0 << " ms."; + + return true; +} + +std::unique_ptr ConfigManager::Create() { + std::unique_ptr cfgMgr(new ConfigManager()); + + /* + * Read a configuration from XML file + * + * If this is too slow, ConfigManager::readConfigDataFromBinary() and + * ConfigManager::writeConfigDataToBinary()can serialize CameraInfo object + * to the filesystem and construct CameraInfo instead; this was + * evaluated as 10x faster. + */ + if (!cfgMgr->readConfigDataFromXML()) { + return nullptr; + } else { + return cfgMgr; + } +} + +ConfigManager::CameraInfo::~CameraInfo() { + free_camera_metadata(characteristics); + + for (auto&& [tag, val] : cameraMetadata) { + switch (tag) { + case ANDROID_LENS_DISTORTION: + case ANDROID_LENS_POSE_ROTATION: + case ANDROID_LENS_POSE_TRANSLATION: + case ANDROID_LENS_INTRINSIC_CALIBRATION: { + delete[] reinterpret_cast(val.first); + break; + } + + case ANDROID_REQUEST_AVAILABLE_CAPABILITIES: { + delete[] reinterpret_cast< + camera_metadata_enum_android_request_available_capabilities_t*>(val.first); + break; + } + + case ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS: { + delete[] reinterpret_cast(val.first); + break; + } + + default: + LOG(WARNING) << "Tag " << std::hex << tag << " is not supported. " + << "Data may be corrupted?"; + break; + } + } +} diff --git a/automotive/evs/aidl/impl/default/src/ConfigManagerUtil.cpp b/automotive/evs/aidl/impl/default/src/ConfigManagerUtil.cpp new file mode 100644 index 0000000000..e5fe6efa57 --- /dev/null +++ b/automotive/evs/aidl/impl/default/src/ConfigManagerUtil.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ConfigManagerUtil.h" + +#include +#include +#include +#include + +#include +#include + +#include + +using ::aidl::android::hardware::automotive::evs::CameraParam; +using ::aidl::android::hardware::graphics::common::PixelFormat; + +bool ConfigManagerUtil::convertToEvsCameraParam(const std::string& id, CameraParam& camParam) { + std::string trimmed = ConfigManagerUtil::trimString(id); + bool success = true; + + if (!trimmed.compare("BRIGHTNESS")) { + camParam = CameraParam::BRIGHTNESS; + } else if (!trimmed.compare("CONTRAST")) { + camParam = CameraParam::CONTRAST; + } else if (!trimmed.compare("AUTOGAIN")) { + camParam = CameraParam::AUTOGAIN; + } else if (!trimmed.compare("GAIN")) { + camParam = CameraParam::GAIN; + } else if (!trimmed.compare("AUTO_WHITE_BALANCE")) { + camParam = CameraParam::AUTO_WHITE_BALANCE; + } else if (!trimmed.compare("WHITE_BALANCE_TEMPERATURE")) { + camParam = CameraParam::WHITE_BALANCE_TEMPERATURE; + } else if (!trimmed.compare("SHARPNESS")) { + camParam = CameraParam::SHARPNESS; + } else if (!trimmed.compare("AUTO_EXPOSURE")) { + camParam = CameraParam::AUTO_EXPOSURE; + } else if (!trimmed.compare("ABSOLUTE_EXPOSURE")) { + camParam = CameraParam::ABSOLUTE_EXPOSURE; + } else if (!trimmed.compare("ABSOLUTE_FOCUS")) { + camParam = CameraParam::ABSOLUTE_FOCUS; + } else if (!trimmed.compare("AUTO_FOCUS")) { + camParam = CameraParam::AUTO_FOCUS; + } else if (!trimmed.compare("ABSOLUTE_ZOOM")) { + camParam = CameraParam::ABSOLUTE_ZOOM; + } else { + success = false; + } + + return success; +} + +bool ConfigManagerUtil::convertToPixelFormat(const std::string& in, PixelFormat& out) { + std::string trimmed = ConfigManagerUtil::trimString(in); + bool success = true; + + if (!trimmed.compare("RGBA_8888")) { + out = PixelFormat::RGBA_8888; + } else if (!trimmed.compare("YCRCB_420_SP")) { + out = PixelFormat::YCRCB_420_SP; + } else if (!trimmed.compare("YCBCR_422_I")) { + out = PixelFormat::YCBCR_422_I; + } else { + out = PixelFormat::UNSPECIFIED; + success = false; + } + + return success; +} + +bool ConfigManagerUtil::convertToMetadataTag(const char* name, camera_metadata_tag& aTag) { + if (!std::strcmp(name, "LENS_DISTORTION")) { + aTag = ANDROID_LENS_DISTORTION; + } else if (!std::strcmp(name, "LENS_INTRINSIC_CALIBRATION")) { + aTag = ANDROID_LENS_INTRINSIC_CALIBRATION; + } else if (!std::strcmp(name, "LENS_POSE_ROTATION")) { + aTag = ANDROID_LENS_POSE_ROTATION; + } else if (!std::strcmp(name, "LENS_POSE_TRANSLATION")) { + aTag = ANDROID_LENS_POSE_TRANSLATION; + } else if (!std::strcmp(name, "REQUEST_AVAILABLE_CAPABILITIES")) { + aTag = ANDROID_REQUEST_AVAILABLE_CAPABILITIES; + } else if (!std::strcmp(name, "LOGICAL_MULTI_CAMERA_PHYSICAL_IDS")) { + aTag = ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS; + } else { + return false; + } + + return true; +} + +bool ConfigManagerUtil::convertToCameraCapability( + const char* name, camera_metadata_enum_android_request_available_capabilities_t& cap) { + if (!std::strcmp(name, "DEPTH_OUTPUT")) { + cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT; + } else if (!std::strcmp(name, "LOGICAL_MULTI_CAMERA")) { + cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA; + } else if (!std::strcmp(name, "MONOCHROME")) { + cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME; + } else if (!std::strcmp(name, "SECURE_IMAGE_DATA")) { + cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA; + } else { + return false; + } + + return true; +} + +float* ConfigManagerUtil::convertFloatArray(const char* sz, const char* vals, size_t& count, + const char delimiter) { + std::string size_string(sz); + std::string value_string(vals); + + if (!android::base::ParseUint(size_string, &count)) { + LOG(ERROR) << "Failed to parse " << size_string; + return nullptr; + } + float* result = new float[count]; + std::stringstream values(value_string); + + int32_t idx = 0; + std::string token; + while (getline(values, token, delimiter)) { + if (!android::base::ParseFloat(token, &result[idx++])) { + LOG(WARNING) << "Failed to parse " << token; + } + } + + return result; +} + +std::string ConfigManagerUtil::trimString(const std::string& src, const std::string& ws) { + const auto s = src.find_first_not_of(ws); + if (s == std::string::npos) { + return ""; + } + + const auto e = src.find_last_not_of(ws); + const auto r = e - s + 1; + + return src.substr(s, r); +} diff --git a/automotive/evs/aidl/impl/default/src/DefaultEvsEnumerator.cpp b/automotive/evs/aidl/impl/default/src/DefaultEvsEnumerator.cpp deleted file mode 100644 index 5a81d05850..0000000000 --- a/automotive/evs/aidl/impl/default/src/DefaultEvsEnumerator.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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. - */ - -// TODO(b/203661081): Remove below lines to disable compiler warnings. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-parameter" - -#define LOG_TAG "DefaultEvsEnumerator" - -#include - -namespace aidl::android::hardware::automotive::evs::implementation { - -using ::ndk::ScopedAStatus; - -ScopedAStatus DefaultEvsEnumerator::isHardware(bool* flag) { - // This returns true always. - *flag = true; - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::openCamera(const std::string& cameraId, - const Stream& streamConfig, - std::shared_ptr* obj) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::closeCamera(const std::shared_ptr& obj) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::getCameraList(std::vector* list) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::getStreamList(const CameraDesc& desc, - std::vector* _aidl_return) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::openDisplay(int32_t displayId, - std::shared_ptr* obj) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::closeDisplay(const std::shared_ptr& state) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::getDisplayIdList(std::vector* list) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::getDisplayState(DisplayState* state) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::registerStatusCallback( - const std::shared_ptr& callback) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::openUltrasonicsArray( - const std::string& id, std::shared_ptr* obj) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::closeUltrasonicsArray( - const std::shared_ptr& obj) { - return ScopedAStatus::ok(); -} - -ScopedAStatus DefaultEvsEnumerator::getUltrasonicsArrayList( - std::vector* list) { - return ScopedAStatus::ok(); -} - -} // namespace aidl::android::hardware::automotive::evs::implementation - -#pragma clang diagnostic pop diff --git a/automotive/evs/aidl/impl/default/src/EvsEnumerator.cpp b/automotive/evs/aidl/impl/default/src/EvsEnumerator.cpp new file mode 100644 index 0000000000..6e2405deb5 --- /dev/null +++ b/automotive/evs/aidl/impl/default/src/EvsEnumerator.cpp @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "EvsEnumerator.h" + +#include "ConfigManager.h" +#include "EvsGlDisplay.h" +#include "EvsMockCamera.h" + +#include +#include +#include +#include + +#include +#include + +namespace { + +using ::aidl::android::frameworks::automotive::display::ICarDisplayProxy; +using ::aidl::android::hardware::graphics::common::BufferUsage; +using ::ndk::ScopedAStatus; +using std::chrono_literals::operator""s; + +// Constants +constexpr std::chrono::seconds kEnumerationTimeout = 10s; +constexpr uint64_t kInvalidDisplayId = std::numeric_limits::max(); +const std::set kAllowedUids = {AID_AUTOMOTIVE_EVS, AID_SYSTEM, AID_ROOT}; + +} // namespace + +namespace aidl::android::hardware::automotive::evs::implementation { + +// NOTE: All members values are static so that all clients operate on the same state +// That is to say, this is effectively a singleton despite the fact that HIDL +// constructs a new instance for each client. +std::unordered_map EvsEnumerator::sCameraList; +std::mutex EvsEnumerator::sLock; +std::condition_variable EvsEnumerator::sCameraSignal; +std::unique_ptr EvsEnumerator::sConfigManager; +std::shared_ptr EvsEnumerator::sDisplayProxy; +std::unordered_map EvsEnumerator::sDisplayPortList; + +EvsEnumerator::ActiveDisplays& EvsEnumerator::mutableActiveDisplays() { + static ActiveDisplays active_displays; + return active_displays; +} + +EvsEnumerator::EvsEnumerator(const std::shared_ptr& proxyService) { + LOG(DEBUG) << "EvsEnumerator is created."; + + if (!sConfigManager) { + /* loads and initializes ConfigManager in a separate thread */ + sConfigManager = ConfigManager::Create(); + } + + if (!sDisplayProxy) { + /* sets a car-window service handle */ + sDisplayProxy = proxyService; + } + + // Enumerate existing devices + enumerateCameras(); + mInternalDisplayId = enumerateDisplays(); +} + +bool EvsEnumerator::checkPermission() { + const auto uid = AIBinder_getCallingUid(); + if (kAllowedUids.find(uid) == kAllowedUids.end()) { + LOG(ERROR) << "EVS access denied: " + << "pid = " << AIBinder_getCallingPid() << ", uid = " << uid; + return false; + } + + return true; +} + +void EvsEnumerator::enumerateCameras() { + if (!sConfigManager) { + return; + } + + for (auto id : sConfigManager->getCameraIdList()) { + CameraRecord rec(id.data()); + std::unique_ptr& pInfo = sConfigManager->getCameraInfo(id); + if (pInfo) { + uint8_t* ptr = reinterpret_cast(pInfo->characteristics); + const size_t len = get_camera_metadata_size(pInfo->characteristics); + rec.desc.metadata.insert(rec.desc.metadata.end(), ptr, ptr + len); + } + sCameraList.insert_or_assign(id, std::move(rec)); + } +} + +uint64_t EvsEnumerator::enumerateDisplays() { + LOG(INFO) << __FUNCTION__ << ": Starting display enumeration"; + uint64_t internalDisplayId = kInvalidDisplayId; + if (!sDisplayProxy) { + LOG(ERROR) << "ICarDisplayProxy is not available!"; + return internalDisplayId; + } + + std::vector displayIds; + if (auto status = sDisplayProxy->getDisplayIdList(&displayIds); !status.isOk()) { + LOG(ERROR) << "Failed to retrieve a display id list" + << ::android::statusToString(status.getStatus()); + return internalDisplayId; + } + + if (displayIds.size() > 0) { + // The first entry of the list is the internal display. See + // SurfaceFlinger::getPhysicalDisplayIds() implementation. + internalDisplayId = displayIds[0]; + for (const auto& id : displayIds) { + const auto port = id & 0xFF; + LOG(INFO) << "Display " << std::hex << id << " is detected on the port, " << port; + sDisplayPortList.insert_or_assign(port, id); + } + } + + LOG(INFO) << "Found " << sDisplayPortList.size() << " displays"; + return internalDisplayId; +} + +// Methods from ::android::hardware::automotive::evs::IEvsEnumerator follow. +ScopedAStatus EvsEnumerator::getCameraList(std::vector* _aidl_return) { + LOG(DEBUG) << __FUNCTION__; + if (!checkPermission()) { + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::PERMISSION_DENIED)); + } + + { + std::unique_lock lock(sLock); + if (sCameraList.size() < 1) { + // No qualified device has been found. Wait until new device is ready, + // for 10 seconds. + if (!sCameraSignal.wait_for(lock, kEnumerationTimeout, + [] { return sCameraList.size() > 0; })) { + LOG(DEBUG) << "Timer expired. No new device has been added."; + } + } + } + + // Build up a packed array of CameraDesc for return + _aidl_return->resize(sCameraList.size()); + unsigned i = 0; + for (const auto& [key, cam] : sCameraList) { + (*_aidl_return)[i++] = cam.desc; + } + + if (sConfigManager) { + // Adding camera groups that represent logical camera devices + auto camGroups = sConfigManager->getCameraGroupIdList(); + for (auto&& id : camGroups) { + if (sCameraList.find(id) != sCameraList.end()) { + // Already exists in the _aidl_return + continue; + } + + std::unique_ptr& tempInfo = + sConfigManager->getCameraGroupInfo(id); + CameraRecord cam(id.data()); + if (tempInfo) { + uint8_t* ptr = reinterpret_cast(tempInfo->characteristics); + const size_t len = get_camera_metadata_size(tempInfo->characteristics); + cam.desc.metadata.insert(cam.desc.metadata.end(), ptr, ptr + len); + } + + sCameraList.insert_or_assign(id, cam); + _aidl_return->push_back(cam.desc); + } + } + + // Send back the results + LOG(DEBUG) << "Reporting " << sCameraList.size() << " cameras available"; + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::getStreamList(const CameraDesc& desc, + std::vector* _aidl_return) { + using AidlPixelFormat = ::aidl::android::hardware::graphics::common::PixelFormat; + + camera_metadata_t* pMetadata = const_cast( + reinterpret_cast(desc.metadata.data())); + camera_metadata_entry_t streamConfig; + if (!find_camera_metadata_entry(pMetadata, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, + &streamConfig)) { + const unsigned numStreamConfigs = streamConfig.count / sizeof(StreamConfiguration); + _aidl_return->resize(numStreamConfigs); + const StreamConfiguration* pCurrentConfig = + reinterpret_cast(streamConfig.data.i32); + for (unsigned i = 0; i < numStreamConfigs; ++i, ++pCurrentConfig) { + // Build ::aidl::android::hardware::automotive::evs::Stream from + // StreamConfiguration. + Stream current = { + .id = pCurrentConfig->id, + .streamType = + pCurrentConfig->type == + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT + ? StreamType::INPUT + : StreamType::OUTPUT, + .width = pCurrentConfig->width, + .height = pCurrentConfig->height, + .format = static_cast(pCurrentConfig->format), + .usage = BufferUsage::CAMERA_INPUT, + .rotation = Rotation::ROTATION_0, + }; + + (*_aidl_return)[i] = current; + } + } + + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::openCamera(const std::string& id, const Stream& cfg, + std::shared_ptr* obj) { + LOG(DEBUG) << __FUNCTION__; + if (!checkPermission()) { + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::PERMISSION_DENIED)); + } + + // Is this a recognized camera id? + CameraRecord* pRecord = findCameraById(id); + if (!pRecord) { + LOG(ERROR) << id << " does not exist!"; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } + + // Has this camera already been instantiated by another caller? + std::shared_ptr pActiveCamera = pRecord->activeInstance.lock(); + if (pActiveCamera) { + LOG(WARNING) << "Killing previous camera because of new caller"; + closeCamera(pActiveCamera); + } + + // Construct a camera instance for the caller + if (!sConfigManager) { + pActiveCamera = EvsMockCamera::Create(id.data()); + } else { + pActiveCamera = EvsMockCamera::Create(id.data(), sConfigManager->getCameraInfo(id), &cfg); + } + + pRecord->activeInstance = pActiveCamera; + if (!pActiveCamera) { + LOG(ERROR) << "Failed to create new EvsMockCamera object for " << id; + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::UNDERLYING_SERVICE_ERROR)); + } + + *obj = pActiveCamera; + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::closeCamera(const std::shared_ptr& cameraObj) { + LOG(DEBUG) << __FUNCTION__; + + if (!cameraObj) { + LOG(ERROR) << "Ignoring call to closeCamera with null camera ptr"; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } + + // Get the camera id so we can find it in our list + CameraDesc desc; + auto status = cameraObj->getCameraInfo(&desc); + if (!status.isOk()) { + LOG(ERROR) << "Failed to read a camera descriptor"; + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::UNDERLYING_SERVICE_ERROR)); + } + auto cameraId = desc.id; + closeCamera_impl(cameraObj, cameraId); + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::openDisplay(int32_t id, std::shared_ptr* displayObj) { + LOG(DEBUG) << __FUNCTION__; + if (!checkPermission()) { + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::PERMISSION_DENIED)); + } + + auto& displays = mutableActiveDisplays(); + + if (auto existing_display_search = displays.popDisplay(id)) { + // If we already have a display active, then we need to shut it down so we can + // give exclusive access to the new caller. + std::shared_ptr pActiveDisplay = existing_display_search->displayWeak.lock(); + if (pActiveDisplay) { + LOG(WARNING) << "Killing previous display because of new caller"; + pActiveDisplay->forceShutdown(); + } + } + + // Create a new display interface and return it + uint64_t targetDisplayId = mInternalDisplayId; + auto it = sDisplayPortList.find(id); + if (it != sDisplayPortList.end()) { + targetDisplayId = it->second; + } else { + LOG(WARNING) << "No display is available on the port " << static_cast(id) + << ". The main display " << mInternalDisplayId << " will be used instead"; + } + + // Create a new display interface and return it. + std::shared_ptr pActiveDisplay = + ndk::SharedRefBase::make(sDisplayProxy, targetDisplayId); + + if (auto insert_result = displays.tryInsert(id, pActiveDisplay); !insert_result) { + LOG(ERROR) << "Display ID " << id << " has been used by another caller."; + pActiveDisplay->forceShutdown(); + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::RESOURCE_BUSY)); + } + + LOG(DEBUG) << "Returning new EvsGlDisplay object " << pActiveDisplay.get(); + *displayObj = pActiveDisplay; + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::closeDisplay(const std::shared_ptr& obj) { + LOG(DEBUG) << __FUNCTION__; + + auto& displays = mutableActiveDisplays(); + const auto display_search = displays.popDisplay(obj); + + if (!display_search) { + LOG(WARNING) << "Ignoring close of previously orphaned display - why did a client steal?"; + return ScopedAStatus::ok(); + } + + auto pActiveDisplay = display_search->displayWeak.lock(); + + if (!pActiveDisplay) { + LOG(ERROR) << "Somehow a display is being destroyed " + << "when the enumerator didn't know one existed"; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::OWNERSHIP_LOST)); + } + + pActiveDisplay->forceShutdown(); + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::getDisplayState(DisplayState* state) { + LOG(DEBUG) << __FUNCTION__; + if (!checkPermission()) { + *state = DisplayState::DEAD; + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::PERMISSION_DENIED)); + } + + // TODO(b/262779341): For now we can just return the state of the 1st display. Need to update + // the API later. + + const auto& all_displays = mutableActiveDisplays().getAllDisplays(); + + // Do we still have a display object we think should be active? + if (all_displays.empty()) { + *state = DisplayState::NOT_OPEN; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::OWNERSHIP_LOST)); + } + + std::shared_ptr pActiveDisplay = all_displays.begin()->second.displayWeak.lock(); + if (pActiveDisplay) { + return pActiveDisplay->getDisplayState(state); + } else { + *state = DisplayState::NOT_OPEN; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::OWNERSHIP_LOST)); + } +} + +ScopedAStatus EvsEnumerator::getDisplayIdList(std::vector* list) { + std::vector& output = *list; + if (sDisplayPortList.size() > 0) { + output.resize(sDisplayPortList.size()); + unsigned i = 0; + output[i++] = mInternalDisplayId & 0xFF; + for (const auto& [port, id] : sDisplayPortList) { + if (mInternalDisplayId != id) { + output[i++] = port; + } + } + } + + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::isHardware(bool* flag) { + *flag = true; + return ScopedAStatus::ok(); +} + +void EvsEnumerator::notifyDeviceStatusChange(const std::string_view& deviceName, + DeviceStatusType type) { + std::lock_guard lock(sLock); + if (!mCallback) { + return; + } + + std::vector status{{.id = std::string(deviceName), .status = type}}; + if (!mCallback->deviceStatusChanged(status).isOk()) { + LOG(WARNING) << "Failed to notify a device status change, name = " << deviceName + << ", type = " << static_cast(type); + } +} + +ScopedAStatus EvsEnumerator::registerStatusCallback( + const std::shared_ptr& callback) { + std::lock_guard lock(sLock); + if (mCallback) { + LOG(INFO) << "Replacing an existing device status callback"; + } + mCallback = callback; + return ScopedAStatus::ok(); +} + +void EvsEnumerator::closeCamera_impl(const std::shared_ptr& pCamera, + const std::string& cameraId) { + // Find the named camera + CameraRecord* pRecord = findCameraById(cameraId); + + // Is the display being destroyed actually the one we think is active? + if (!pRecord) { + LOG(ERROR) << "Asked to close a camera whose name isn't recognized"; + } else { + std::shared_ptr pActiveCamera = pRecord->activeInstance.lock(); + if (!pActiveCamera) { + LOG(WARNING) << "Somehow a camera is being destroyed " + << "when the enumerator didn't know one existed"; + } else if (pActiveCamera != pCamera) { + // This can happen if the camera was aggressively reopened, + // orphaning this previous instance + LOG(WARNING) << "Ignoring close of previously orphaned camera " + << "- why did a client steal?"; + } else { + // Shutdown the active camera + pActiveCamera->shutdown(); + } + } + + return; +} + +EvsEnumerator::CameraRecord* EvsEnumerator::findCameraById(const std::string& cameraId) { + // Find the named camera + auto found = sCameraList.find(cameraId); + if (found != sCameraList.end()) { + // Found a match! + return &found->second; + } + + // We didn't find a match + return nullptr; +} + +std::optional EvsEnumerator::ActiveDisplays::popDisplay( + int32_t id) { + std::lock_guard lck(mMutex); + const auto search = mIdToDisplay.find(id); + if (search == mIdToDisplay.end()) { + return std::nullopt; + } + const auto display_info = search->second; + mIdToDisplay.erase(search); + mDisplayToId.erase(display_info.internalDisplayRawAddr); + return display_info; +} + +std::optional EvsEnumerator::ActiveDisplays::popDisplay( + const std::shared_ptr& display) { + const auto display_ptr_val = reinterpret_cast(display.get()); + std::lock_guard lck(mMutex); + const auto display_to_id_search = mDisplayToId.find(display_ptr_val); + if (display_to_id_search == mDisplayToId.end()) { + LOG(ERROR) << "Unknown display."; + return std::nullopt; + } + const auto id = display_to_id_search->second; + const auto id_to_display_search = mIdToDisplay.find(id); + mDisplayToId.erase(display_to_id_search); + if (id_to_display_search == mIdToDisplay.end()) { + LOG(ERROR) << "No correspsonding ID for the display, probably orphaned."; + return std::nullopt; + } + const auto display_info = id_to_display_search->second; + mIdToDisplay.erase(id); + return display_info; +} + +std::unordered_map +EvsEnumerator::ActiveDisplays::getAllDisplays() { + std::lock_guard lck(mMutex); + auto id_to_display_map_copy = mIdToDisplay; + return id_to_display_map_copy; +} + +bool EvsEnumerator::ActiveDisplays::tryInsert(int32_t id, + const std::shared_ptr& display) { + std::lock_guard lck(mMutex); + const auto display_ptr_val = reinterpret_cast(display.get()); + + auto id_to_display_insert_result = + mIdToDisplay.emplace(id, DisplayInfo{ + .id = id, + .displayWeak = display, + .internalDisplayRawAddr = display_ptr_val, + }); + if (!id_to_display_insert_result.second) { + return false; + } + auto display_to_id_insert_result = mDisplayToId.emplace(display_ptr_val, id); + if (!display_to_id_insert_result.second) { + mIdToDisplay.erase(id); + return false; + } + return true; +} + +ScopedAStatus EvsEnumerator::getUltrasonicsArrayList( + [[maybe_unused]] std::vector* list) { + // TODO(b/149874793): Add implementation for EVS Manager and Sample driver + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::openUltrasonicsArray( + [[maybe_unused]] const std::string& id, + [[maybe_unused]] std::shared_ptr* obj) { + // TODO(b/149874793): Add implementation for EVS Manager and Sample driver + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsEnumerator::closeUltrasonicsArray( + [[maybe_unused]] const std::shared_ptr& obj) { + // TODO(b/149874793): Add implementation for EVS Manager and Sample driver + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::automotive::evs::implementation diff --git a/automotive/evs/aidl/impl/default/src/EvsGlDisplay.cpp b/automotive/evs/aidl/impl/default/src/EvsGlDisplay.cpp new file mode 100644 index 0000000000..e5f8e4c84a --- /dev/null +++ b/automotive/evs/aidl/impl/default/src/EvsGlDisplay.cpp @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "EvsGlDisplay.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using ::aidl::android::frameworks::automotive::display::ICarDisplayProxy; +using ::aidl::android::hardware::graphics::common::BufferUsage; +using ::aidl::android::hardware::graphics::common::PixelFormat; +using ::android::base::ScopedLockAssertion; +using ::ndk::ScopedAStatus; + +constexpr auto kTimeout = std::chrono::seconds(1); + +bool debugFirstFrameDisplayed = false; + +int generateFingerPrint(buffer_handle_t handle) { + return static_cast(reinterpret_cast(handle) & 0xFFFFFFFF); +} + +} // namespace + +namespace aidl::android::hardware::automotive::evs::implementation { + +EvsGlDisplay::EvsGlDisplay(const std::shared_ptr& pDisplayProxy, + uint64_t displayId) + : mDisplayId(displayId), mDisplayProxy(pDisplayProxy) { + LOG(DEBUG) << "EvsGlDisplay instantiated"; + + // Set up our self description + // NOTE: These are arbitrary values chosen for testing + mInfo.id = std::to_string(displayId); + mInfo.vendorFlags = 3870; + + // Start a thread to render images on this display + { + std::lock_guard lock(mLock); + mState = RUN; + } + mRenderThread = std::thread([this]() { renderFrames(); }); +} + +EvsGlDisplay::~EvsGlDisplay() { + LOG(DEBUG) << "EvsGlDisplay being destroyed"; + forceShutdown(); +} + +/** + * This gets called if another caller "steals" ownership of the display + */ +void EvsGlDisplay::forceShutdown() { + LOG(DEBUG) << "EvsGlDisplay forceShutdown"; + { + std::lock_guard lock(mLock); + + // If the buffer isn't being held by a remote client, release it now as an + // optimization to release the resources more quickly than the destructor might + // get called. + if (mBuffer.handle != nullptr) { + // Report if we're going away while a buffer is outstanding + if (mBufferBusy || mState == RUN) { + LOG(ERROR) << "EvsGlDisplay going down while client is holding a buffer"; + } + mState = STOPPING; + } + + // Put this object into an unrecoverable error state since somebody else + // is going to own the display now. + mRequestedState = DisplayState::DEAD; + } + mBufferReadyToRender.notify_all(); + + if (mRenderThread.joinable()) { + mRenderThread.join(); + } +} + +/** + * Initialize GL in the context of a caller's thread and prepare a graphic + * buffer to use. + */ +bool EvsGlDisplay::initializeGlContextLocked() { + // Initialize our display window + // NOTE: This will cause the display to become "VISIBLE" before a frame is actually + // returned, which is contrary to the spec and will likely result in a black frame being + // (briefly) shown. + if (!mGlWrapper.initialize(mDisplayProxy, mDisplayId)) { + // Report the failure + LOG(ERROR) << "Failed to initialize GL display"; + return false; + } + + // Assemble the buffer description we'll use for our render target + static_assert(::aidl::android::hardware::graphics::common::PixelFormat::RGBA_8888 == + static_cast<::aidl::android::hardware::graphics::common::PixelFormat>( + HAL_PIXEL_FORMAT_RGBA_8888)); + mBuffer.description = { + .width = static_cast(mGlWrapper.getWidth()), + .height = static_cast(mGlWrapper.getHeight()), + .layers = 1, + .format = PixelFormat::RGBA_8888, + // FIXME: Below line is not using + // ::aidl::android::hardware::graphics::common::BufferUsage because + // BufferUsage enum does not support a bitwise-OR operation; they + // should be BufferUsage::GPU_RENDER_TARGET | + // BufferUsage::COMPOSER_OVERLAY + .usage = static_cast(GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER), + }; + + ::android::GraphicBufferAllocator& alloc(::android::GraphicBufferAllocator::get()); + uint32_t stride = static_cast(mBuffer.description.stride); + buffer_handle_t handle = nullptr; + const ::android::status_t result = + alloc.allocate(mBuffer.description.width, mBuffer.description.height, + static_cast<::android::PixelFormat>(mBuffer.description.format), + mBuffer.description.layers, + static_cast(mBuffer.description.usage), &handle, &stride, + /* requestorName= */ "EvsGlDisplay"); + mBuffer.description.stride = stride; + mBuffer.fingerprint = generateFingerPrint(mBuffer.handle); + if (result != ::android::NO_ERROR) { + LOG(ERROR) << "Error " << result << " allocating " << mBuffer.description.width << " x " + << mBuffer.description.height << " graphics buffer."; + mGlWrapper.shutdown(); + return false; + } + + mBuffer.handle = handle; + if (mBuffer.handle == nullptr) { + LOG(ERROR) << "We didn't get a buffer handle back from the allocator"; + mGlWrapper.shutdown(); + return false; + } + + LOG(DEBUG) << "Allocated new buffer " << mBuffer.handle << " with stride " + << mBuffer.description.stride; + return true; +} + +/** + * This method runs in a separate thread and renders the contents of the buffer. + */ +void EvsGlDisplay::renderFrames() { + { + std::lock_guard lock(mLock); + + if (!initializeGlContextLocked()) { + LOG(ERROR) << "Failed to initialize GL context"; + return; + } + + // Display buffer is ready. + mBufferBusy = false; + } + mBufferReadyToUse.notify_all(); + + while (true) { + { + std::unique_lock lock(mLock); + ScopedLockAssertion lock_assertion(mLock); + mBufferReadyToRender.wait( + lock, [this]() REQUIRES(mLock) { return mBufferReady || mState != RUN; }); + if (mState != RUN) { + LOG(DEBUG) << "A rendering thread is stopping"; + break; + } + mBufferReady = false; + } + + // Update the texture contents with the provided data + if (!mGlWrapper.updateImageTexture(mBuffer.handle, mBuffer.description)) { + LOG(WARNING) << "Failed to update the image texture"; + continue; + } + + // Put the image on the screen + mGlWrapper.renderImageToScreen(); + if (!debugFirstFrameDisplayed) { + LOG(DEBUG) << "EvsFirstFrameDisplayTiming start time: " << ::android::elapsedRealtime() + << " ms."; + debugFirstFrameDisplayed = true; + } + + // Mark current frame is consumed. + { + std::lock_guard lock(mLock); + mBufferBusy = false; + } + mBufferDone.notify_all(); + } + + LOG(DEBUG) << "A rendering thread is stopped."; + + // Drop the graphics buffer we've been using + ::android::GraphicBufferAllocator& alloc(::android::GraphicBufferAllocator::get()); + alloc.free(mBuffer.handle); + mBuffer.handle = nullptr; + + mGlWrapper.hideWindow(mDisplayProxy, mDisplayId); + mGlWrapper.shutdown(); + + std::lock_guard lock(mLock); + mState = STOPPED; +} + +/** + * Returns basic information about the EVS display provided by the system. + * See the description of the DisplayDesc structure for details. + */ +ScopedAStatus EvsGlDisplay::getDisplayInfo(DisplayDesc* _aidl_return) { + if (!mDisplayProxy) { + return ::ndk::ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::UNDERLYING_SERVICE_ERROR)); + } + + ::aidl::android::frameworks::automotive::display::DisplayDesc proxyDisplay; + auto status = mDisplayProxy->getDisplayInfo(mDisplayId, &proxyDisplay); + if (!status.isOk()) { + return ::ndk::ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::UNDERLYING_SERVICE_ERROR)); + } + + _aidl_return->width = proxyDisplay.width; + _aidl_return->height = proxyDisplay.height; + _aidl_return->orientation = static_cast(proxyDisplay.orientation); + _aidl_return->id = mInfo.id; // FIXME: what should be ID here? + _aidl_return->vendorFlags = mInfo.vendorFlags; + return ::ndk::ScopedAStatus::ok(); +} + +/** + * Clients may set the display state to express their desired state. + * The HAL implementation must gracefully accept a request for any state + * while in any other state, although the response may be to ignore the request. + * The display is defined to start in the NOT_VISIBLE state upon initialization. + * The client is then expected to request the VISIBLE_ON_NEXT_FRAME state, and + * then begin providing video. When the display is no longer required, the client + * is expected to request the NOT_VISIBLE state after passing the last video frame. + */ +ScopedAStatus EvsGlDisplay::setDisplayState(DisplayState state) { + LOG(DEBUG) << __FUNCTION__; + std::lock_guard lock(mLock); + + if (mRequestedState == DisplayState::DEAD) { + // This object no longer owns the display -- it's been superceeded! + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::OWNERSHIP_LOST)); + } + + // Ensure we recognize the requested state so we don't go off the rails + static constexpr ::ndk::enum_range kDisplayStateRange; + if (std::find(kDisplayStateRange.begin(), kDisplayStateRange.end(), state) == + kDisplayStateRange.end()) { + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } + + switch (state) { + case DisplayState::NOT_VISIBLE: + mGlWrapper.hideWindow(mDisplayProxy, mDisplayId); + break; + case DisplayState::VISIBLE: + mGlWrapper.showWindow(mDisplayProxy, mDisplayId); + break; + default: + break; + } + + // Record the requested state + mRequestedState = state; + + return ScopedAStatus::ok(); +} + +/** + * The HAL implementation should report the actual current state, which might + * transiently differ from the most recently requested state. Note, however, that + * the logic responsible for changing display states should generally live above + * the device layer, making it undesirable for the HAL implementation to + * spontaneously change display states. + */ +ScopedAStatus EvsGlDisplay::getDisplayState(DisplayState* _aidl_return) { + LOG(DEBUG) << __FUNCTION__; + std::lock_guard lock(mLock); + *_aidl_return = mRequestedState; + return ScopedAStatus::ok(); +} + +/** + * This call returns a handle to a frame buffer associated with the display. + * This buffer may be locked and written to by software and/or GL. This buffer + * must be returned via a call to returnTargetBufferForDisplay() even if the + * display is no longer visible. + */ +ScopedAStatus EvsGlDisplay::getTargetBuffer(BufferDesc* _aidl_return) { + LOG(DEBUG) << __FUNCTION__; + std::unique_lock lock(mLock); + ScopedLockAssertion lock_assertion(mLock); + if (mRequestedState == DisplayState::DEAD) { + LOG(ERROR) << "Rejecting buffer request from object that lost ownership of the display."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::OWNERSHIP_LOST)); + } + + // If we don't already have a buffer, allocate one now + // mBuffer.memHandle is a type of buffer_handle_t, which is equal to + // native_handle_t*. + mBufferReadyToUse.wait(lock, [this]() REQUIRES(mLock) { return !mBufferBusy; }); + + // Do we have a frame available? + if (mBufferBusy) { + // This means either we have a 2nd client trying to compete for buffers + // (an unsupported mode of operation) or else the client hasn't returned + // a previously issued buffer yet (they're behaving badly). + // NOTE: We have to make the callback even if we have nothing to provide + LOG(ERROR) << "getTargetBuffer called while no buffers available."; + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::BUFFER_NOT_AVAILABLE)); + } + + // Mark our buffer as busy + mBufferBusy = true; + + // Send the buffer to the client + LOG(VERBOSE) << "Providing display buffer handle " << mBuffer.handle; + + BufferDesc bufferDescToSend = { + .buffer = + { + .handle = std::move(::android::dupToAidl(mBuffer.handle)), + .description = mBuffer.description, + }, + .pixelSizeBytes = 4, // RGBA_8888 is 4-byte-per-pixel format + .bufferId = mBuffer.fingerprint, + }; + *_aidl_return = std::move(bufferDescToSend); + + return ScopedAStatus::ok(); +} + +/** + * This call tells the display that the buffer is ready for display. + * The buffer is no longer valid for use by the client after this call. + */ +ScopedAStatus EvsGlDisplay::returnTargetBufferForDisplay(const BufferDesc& buffer) { + LOG(VERBOSE) << __FUNCTION__; + std::unique_lock lock(mLock); + ScopedLockAssertion lock_assertion(mLock); + + // Nobody should call us with a null handle + if (buffer.buffer.handle.fds.size() < 1) { + LOG(ERROR) << __FUNCTION__ << " called without a valid buffer handle."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } + if (buffer.bufferId != mBuffer.fingerprint) { + LOG(ERROR) << "Got an unrecognized frame returned."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } + if (!mBufferBusy) { + LOG(ERROR) << "A frame was returned with no outstanding frames."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } + + // If we've been displaced by another owner of the display, then we can't do anything else + if (mRequestedState == DisplayState::DEAD) { + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::OWNERSHIP_LOST)); + } + + // If we were waiting for a new frame, this is it! + if (mRequestedState == DisplayState::VISIBLE_ON_NEXT_FRAME) { + mRequestedState = DisplayState::VISIBLE; + mGlWrapper.showWindow(mDisplayProxy, mDisplayId); + } + + // Validate we're in an expected state + if (mRequestedState != DisplayState::VISIBLE) { + // Not sure why a client would send frames back when we're not visible. + LOG(WARNING) << "Got a frame returned while not visible - ignoring."; + return ScopedAStatus::ok(); + } + mBufferReady = true; + mBufferReadyToRender.notify_all(); + + if (!mBufferDone.wait_for(lock, kTimeout, [this]() REQUIRES(mLock) { return !mBufferBusy; })) { + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::UNDERLYING_SERVICE_ERROR)); + } + + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::automotive::evs::implementation diff --git a/automotive/evs/aidl/impl/default/src/EvsMockCamera.cpp b/automotive/evs/aidl/impl/default/src/EvsMockCamera.cpp new file mode 100644 index 0000000000..4b46a5ae64 --- /dev/null +++ b/automotive/evs/aidl/impl/default/src/EvsMockCamera.cpp @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "EvsMockCamera.h" +#include "ConfigManager.h" +#include "EvsEnumerator.h" + +#include +#include +#include +#include + +#include + +namespace { + +using ::aidl::android::hardware::graphics::common::BufferUsage; +using ::ndk::ScopedAStatus; + +// Arbitrary limit on number of graphics buffers allowed to be allocated +// Safeguards against unreasonable resource consumption and provides a testable limit +constexpr unsigned kMaxBuffersInFlight = 100; + +// Minimum number of buffers to run a video stream +constexpr int kMinimumBuffersInFlight = 1; + +// Colors for the colorbar test pattern in ABGR format +constexpr uint32_t kColors[] = { + 0xFFFFFFFF, // white + 0xFF00FFFF, // yellow + 0xFFFFFF00, // cyan + 0xFF00FF00, // green + 0xFFFF00FF, // fuchsia + 0xFF0000FF, // red + 0xFFFF0000, // blue + 0xFF000000, // black +}; +constexpr size_t kNumColors = sizeof(kColors) / sizeof(kColors[0]); + +} // namespace + +namespace aidl::android::hardware::automotive::evs::implementation { + +EvsMockCamera::EvsMockCamera([[maybe_unused]] Sigil sigil, const char* id, + std::unique_ptr& camInfo) + : mFramesAllowed(0), mFramesInUse(0), mStreamState(STOPPED), mCameraInfo(camInfo) { + LOG(DEBUG) << __FUNCTION__; + + /* set a camera id */ + mDescription.id = id; + + /* set camera metadata */ + if (camInfo) { + uint8_t* ptr = reinterpret_cast(camInfo->characteristics); + const size_t len = get_camera_metadata_size(camInfo->characteristics); + mDescription.metadata.insert(mDescription.metadata.end(), ptr, ptr + len); + } +} + +EvsMockCamera::~EvsMockCamera() { + LOG(DEBUG) << __FUNCTION__; + shutdown(); +} + +// This gets called if another caller "steals" ownership of the camera +void EvsMockCamera::shutdown() { + LOG(DEBUG) << __FUNCTION__; + + // Make sure our output stream is cleaned up + // (It really should be already) + stopVideoStream_impl(); + + // Claim the lock while we work on internal state + std::lock_guard lock(mAccessLock); + + // Drop all the graphics buffers we've been using + if (mBuffers.size() > 0) { + ::android::GraphicBufferAllocator& alloc(::android::GraphicBufferAllocator::get()); + for (auto&& rec : mBuffers) { + if (rec.inUse) { + LOG(WARNING) << "WARNING: releasing a buffer remotely owned."; + } + alloc.free(rec.handle); + rec.handle = nullptr; + } + mBuffers.clear(); + } + + // Put this object into an unrecoverable error state since somebody else + // is going to own the underlying camera now + mStreamState = DEAD; +} + +// Methods from ::aidl::android::hardware::automotive::evs::IEvsCamera follow. +ScopedAStatus EvsMockCamera::getCameraInfo(CameraDesc* _aidl_return) { + LOG(DEBUG) << __FUNCTION__; + + // Send back our self description + *_aidl_return = mDescription; + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::setMaxFramesInFlight(int32_t bufferCount) { + LOG(DEBUG) << __FUNCTION__ << ", bufferCount = " << bufferCount; + ; + + std::lock_guard lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (mStreamState == DEAD) { + LOG(ERROR) << "Ignoring setMaxFramesInFlight call when camera has been lost."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::OWNERSHIP_LOST)); + } + + // We cannot function without at least one video buffer to send data + if (bufferCount < 1) { + LOG(ERROR) << "Ignoring setMaxFramesInFlight with less than one buffer requested."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } + + // Update our internal state + if (!setAvailableFrames_Locked(bufferCount)) { + LOG(ERROR) << "Failed to adjust the maximum number of frames in flight."; + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::BUFFER_NOT_AVAILABLE)); + } + + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::startVideoStream(const std::shared_ptr& cb) { + LOG(DEBUG) << __FUNCTION__; + + if (!cb) { + LOG(ERROR) << "A given stream callback is invalid."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } + + std::lock_guard lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (mStreamState == DEAD) { + LOG(ERROR) << "Ignoring startVideoStream call when camera has been lost."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::OWNERSHIP_LOST)); + } + + if (mStreamState != STOPPED) { + LOG(ERROR) << "Ignoring startVideoStream call when a stream is already running."; + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::STREAM_ALREADY_RUNNING)); + } + + // If the client never indicated otherwise, configure ourselves for a single streaming buffer + if (mFramesAllowed < kMinimumBuffersInFlight && + !setAvailableFrames_Locked(kMinimumBuffersInFlight)) { + LOG(ERROR) << "Failed to start stream because we couldn't get a graphics buffer"; + return ScopedAStatus::fromServiceSpecificError( + static_cast(EvsResult::BUFFER_NOT_AVAILABLE)); + } + + // Record the user's callback for use when we have a frame ready + mStream = cb; + + // Start the frame generation thread + mStreamState = RUNNING; + mCaptureThread = std::thread([this]() { generateFrames(); }); + + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::doneWithFrame(const std::vector& list) { + std::lock_guard lock(mAccessLock); + for (const auto& desc : list) { + returnBufferLocked(desc.bufferId); + } + + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::stopVideoStream() { + LOG(DEBUG) << __FUNCTION__; + return stopVideoStream_impl(); +} + +ScopedAStatus EvsMockCamera::stopVideoStream_impl() { + std::unique_lock lock(mAccessLock); + + if (mStreamState != RUNNING) { + // Safely return here because a stream is not running. + return ScopedAStatus::ok(); + } + + // Tell the GenerateFrames loop we want it to stop + mStreamState = STOPPING; + + // Block outside the mutex until the "stop" flag has been acknowledged + // We won't send any more frames, but the client might still get some already in flight + LOG(DEBUG) << "Waiting for stream thread to end..."; + lock.unlock(); + if (mCaptureThread.joinable()) { + mCaptureThread.join(); + } + lock.lock(); + + mStreamState = STOPPED; + mStream = nullptr; + LOG(DEBUG) << "Stream marked STOPPED."; + + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::getExtendedInfo(int32_t opaqueIdentifier, + std::vector* opaqueValue) { + const auto it = mExtInfo.find(opaqueIdentifier); + if (it == mExtInfo.end()) { + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::INVALID_ARG)); + } else { + *opaqueValue = mExtInfo[opaqueIdentifier]; + } + + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::setExtendedInfo(int32_t opaqueIdentifier, + const std::vector& opaqueValue) { + mExtInfo.insert_or_assign(opaqueIdentifier, opaqueValue); + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::getPhysicalCameraInfo([[maybe_unused]] const std::string& id, + CameraDesc* _aidl_return) { + LOG(DEBUG) << __FUNCTION__; + + // This method works exactly same as getCameraInfo() in EVS HW module. + *_aidl_return = mDescription; + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::pauseVideoStream() { + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::NOT_SUPPORTED)); +} + +ScopedAStatus EvsMockCamera::resumeVideoStream() { + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::NOT_SUPPORTED)); +} + +ScopedAStatus EvsMockCamera::setPrimaryClient() { + /* Because EVS HW module reference implementation expects a single client at + * a time, this returns a success code always. + */ + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::forcePrimaryClient(const std::shared_ptr&) { + /* Because EVS HW module reference implementation expects a single client at + * a time, this returns a success code always. + */ + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::unsetPrimaryClient() { + /* Because EVS HW module reference implementation expects a single client at + * a time, there is no chance that this is called by the secondary client and + * therefore returns a success code always. + */ + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::getParameterList(std::vector* _aidl_return) { + if (mCameraInfo) { + _aidl_return->resize(mCameraInfo->controls.size()); + auto idx = 0; + for (auto& [name, range] : mCameraInfo->controls) { + (*_aidl_return)[idx++] = name; + } + } + + return ScopedAStatus::ok(); +} + +ScopedAStatus EvsMockCamera::getIntParameterRange([[maybe_unused]] CameraParam id, + [[maybe_unused]] ParameterRange* _aidl_return) { + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::NOT_SUPPORTED)); +} + +ScopedAStatus EvsMockCamera::setIntParameter( + [[maybe_unused]] CameraParam id, [[maybe_unused]] int32_t value, + [[maybe_unused]] std::vector* effectiveValue) { + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::NOT_SUPPORTED)); +} + +ScopedAStatus EvsMockCamera::getIntParameter([[maybe_unused]] CameraParam id, + [[maybe_unused]] std::vector* value) { + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::NOT_SUPPORTED)); +} + +ScopedAStatus EvsMockCamera::importExternalBuffers( + [[maybe_unused]] const std::vector& buffers, + [[maybe_unused]] int32_t* _aidl_return) { + LOG(DEBUG) << "This implementation does not support an external buffer import."; + return ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::NOT_SUPPORTED)); +} + +bool EvsMockCamera::setAvailableFrames_Locked(unsigned bufferCount) { + if (bufferCount < 1) { + LOG(ERROR) << "Ignoring request to set buffer count to zero"; + return false; + } + if (bufferCount > kMaxBuffersInFlight) { + LOG(ERROR) << "Rejecting buffer request in excess of internal limit"; + return false; + } + + // Is an increase required? + if (mFramesAllowed < bufferCount) { + // An increase is required + auto needed = bufferCount - mFramesAllowed; + LOG(INFO) << "Allocating " << needed << " buffers for camera frames"; + + auto added = increaseAvailableFrames_Locked(needed); + if (added != needed) { + // If we didn't add all the frames we needed, then roll back to the previous state + LOG(ERROR) << "Rolling back to previous frame queue size"; + decreaseAvailableFrames_Locked(added); + return false; + } + } else if (mFramesAllowed > bufferCount) { + // A decrease is required + auto framesToRelease = mFramesAllowed - bufferCount; + LOG(INFO) << "Returning " << framesToRelease << " camera frame buffers"; + + auto released = decreaseAvailableFrames_Locked(framesToRelease); + if (released != framesToRelease) { + // This shouldn't happen with a properly behaving client because the client + // should only make this call after returning sufficient outstanding buffers + // to allow a clean resize. + LOG(ERROR) << "Buffer queue shrink failed -- too many buffers currently in use?"; + } + } + + return true; +} + +unsigned EvsMockCamera::increaseAvailableFrames_Locked(unsigned numToAdd) { + // Acquire the graphics buffer allocator + ::android::GraphicBufferAllocator& alloc(::android::GraphicBufferAllocator::get()); + + unsigned added = 0; + while (added < numToAdd) { + unsigned pixelsPerLine = 0; + buffer_handle_t memHandle = nullptr; + auto result = alloc.allocate(mWidth, mHeight, mFormat, 1, mUsage, &memHandle, + &pixelsPerLine, 0, "EvsMockCamera"); + if (result != ::android::NO_ERROR) { + LOG(ERROR) << "Error " << result << " allocating " << mWidth << " x " << mHeight + << " graphics buffer"; + break; + } + if (memHandle == nullptr) { + LOG(ERROR) << "We didn't get a buffer handle back from the allocator"; + break; + } + if (mStride > 0) { + if (mStride != pixelsPerLine) { + LOG(ERROR) << "We did not expect to get buffers with different strides!"; + } + } else { + // Gralloc defines stride in terms of pixels per line + mStride = pixelsPerLine; + } + + // Find a place to store the new buffer + auto stored = false; + for (auto&& rec : mBuffers) { + if (rec.handle == nullptr) { + // Use this existing entry + rec.handle = memHandle; + rec.inUse = false; + stored = true; + break; + } + } + if (!stored) { + // Add a BufferRecord wrapping this handle to our set of available buffers + mBuffers.push_back(BufferRecord(memHandle)); + } + + ++mFramesAllowed; + ++added; + } + + return added; +} + +unsigned EvsMockCamera::decreaseAvailableFrames_Locked(unsigned numToRemove) { + // Acquire the graphics buffer allocator + ::android::GraphicBufferAllocator& alloc(::android::GraphicBufferAllocator::get()); + + unsigned removed = 0; + for (auto&& rec : mBuffers) { + // Is this record not in use, but holding a buffer that we can free? + if ((rec.inUse == false) && (rec.handle != nullptr)) { + // Release buffer and update the record so we can recognize it as "empty" + alloc.free(rec.handle); + rec.handle = nullptr; + + --mFramesAllowed; + ++removed; + + if (removed == numToRemove) { + break; + } + } + } + + return removed; +} + +// This is the asynchronous frame generation thread that runs in parallel with the +// main serving thread. There is one for each active camera instance. +void EvsMockCamera::generateFrames() { + LOG(DEBUG) << "Frame generation loop started."; + + unsigned idx = 0; + while (true) { + bool timeForFrame = false; + const nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); + + // Lock scope for updating shared state + { + std::lock_guard lock(mAccessLock); + + if (mStreamState != RUNNING) { + // Break out of our main thread loop + break; + } + + // Are we allowed to issue another buffer? + if (mFramesInUse >= mFramesAllowed) { + // Can't do anything right now -- skip this frame + LOG(WARNING) << "Skipped a frame because too many are in flight."; + } else { + // Identify an available buffer to fill + for (idx = 0; idx < mBuffers.size(); idx++) { + if (!mBuffers[idx].inUse) { + if (mBuffers[idx].handle != nullptr) { + // Found an available record, so stop looking + break; + } + } + } + if (idx >= mBuffers.size()) { + // This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed + ALOGE("Failed to find an available buffer slot\n"); + } else { + // We're going to make the frame busy + mBuffers[idx].inUse = true; + mFramesInUse++; + timeForFrame = true; + } + } + } + + if (timeForFrame) { + using AidlPixelFormat = ::aidl::android::hardware::graphics::common::PixelFormat; + + // Assemble the buffer description we'll transmit below + buffer_handle_t memHandle = mBuffers[idx].handle; + BufferDesc newBuffer = { + .buffer = + { + .description = + { + .width = static_cast(mWidth), + .height = static_cast(mHeight), + .layers = 1, + .format = static_cast(mFormat), + .usage = static_cast(mUsage), + .stride = static_cast(mStride), + }, + .handle = ::android::dupToAidl(memHandle), + }, + .bufferId = static_cast(idx), + .deviceId = mDescription.id, + .timestamp = static_cast(::android::elapsedRealtimeNano() * + 1e+3), // timestamps is in microseconds + }; + + // Write test data into the image buffer + fillMockFrame(memHandle, reinterpret_cast( + &newBuffer.buffer.description)); + + // Issue the (asynchronous) callback to the client -- can't be holding the lock + auto flag = false; + if (mStream) { + std::vector frames; + frames.push_back(std::move(newBuffer)); + flag = mStream->deliverFrame(frames).isOk(); + } + + if (flag) { + LOG(DEBUG) << "Delivered " << memHandle << ", id = " << mBuffers[idx].handle; + } else { + // This can happen if the client dies and is likely unrecoverable. + // To avoid consuming resources generating failing calls, we stop sending + // frames. Note, however, that the stream remains in the "STREAMING" state + // until cleaned up on the main thread. + LOG(ERROR) << "Frame delivery call failed in the transport layer."; + + // Since we didn't actually deliver it, mark the frame as available + std::lock_guard lock(mAccessLock); + mBuffers[idx].inUse = false; + mFramesInUse--; + } + } + + // We arbitrarily choose to generate frames at 15 fps to ensure we pass the 10fps test + // requirement + static const int kTargetFrameRate = 15; + static const nsecs_t kTargetFrameIntervalUs = 1000 * 1000 / kTargetFrameRate; + const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + const nsecs_t elapsedTimeUs = (now - startTime) / 1000; + const nsecs_t sleepDurationUs = kTargetFrameIntervalUs - elapsedTimeUs; + if (sleepDurationUs > 0) { + usleep(sleepDurationUs); + } + } + + // If we've been asked to stop, send an event to signal the actual end of stream + EvsEventDesc event = { + .aType = EvsEventType::STREAM_STOPPED, + }; + if (!mStream->notify(event).isOk()) { + ALOGE("Error delivering end of stream marker"); + } + + return; +} + +void EvsMockCamera::fillMockFrame(buffer_handle_t handle, const AHardwareBuffer_Desc* pDesc) { + // Lock our output buffer for writing + uint32_t* pixels = nullptr; + ::android::GraphicBufferMapper& mapper = ::android::GraphicBufferMapper::get(); + mapper.lock(handle, GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER, + ::android::Rect(pDesc->width, pDesc->height), (void**)&pixels); + + // If we failed to lock the pixel buffer, we're about to crash, but log it first + if (!pixels) { + ALOGE("Camera failed to gain access to image buffer for writing"); + return; + } + + // Fill in the test pixels; the colorbar in ABGR format + for (unsigned row = 0; row < pDesc->height; row++) { + for (unsigned col = 0; col < pDesc->width; col++) { + const uint32_t index = col * kNumColors / pDesc->width; + pixels[col] = kColors[index]; + } + // Point to the next row + // NOTE: stride retrieved from gralloc is in units of pixels + pixels = pixels + pDesc->stride; + } + + // Release our output buffer + mapper.unlock(handle); +} + +void EvsMockCamera::returnBufferLocked(const uint32_t bufferId) { + if (bufferId >= mBuffers.size()) { + ALOGE("ignoring doneWithFrame called with invalid bufferId %d (max is %zu)", bufferId, + mBuffers.size() - 1); + return; + } + + if (!mBuffers[bufferId].inUse) { + ALOGE("ignoring doneWithFrame called on frame %d which is already free", bufferId); + return; + } + + // Mark the frame as available + mBuffers[bufferId].inUse = false; + mFramesInUse--; + + // If this frame's index is high in the array, try to move it down + // to improve locality after mFramesAllowed has been reduced. + if (bufferId >= mFramesAllowed) { + // Find an empty slot lower in the array (which should always exist in this case) + for (auto&& rec : mBuffers) { + if (rec.handle == nullptr) { + rec.handle = mBuffers[bufferId].handle; + mBuffers[bufferId].handle = nullptr; + break; + } + } + } +} + +std::shared_ptr EvsMockCamera::Create(const char* deviceName) { + std::unique_ptr nullCamInfo = nullptr; + + return Create(deviceName, nullCamInfo); +} + +std::shared_ptr EvsMockCamera::Create( + const char* deviceName, std::unique_ptr& camInfo, + [[maybe_unused]] const Stream* streamCfg) { + std::shared_ptr c = + ndk::SharedRefBase::make(Sigil{}, deviceName, camInfo); + if (!c) { + LOG(ERROR) << "Failed to instantiate EvsMockCamera."; + return nullptr; + } + + // Use the first resolution from the list for the testing + // TODO(b/214835237): Uses a given Stream configuration to choose the best + // stream configuration. + auto it = camInfo->streamConfigurations.begin(); + c->mWidth = it->second.width; + c->mHeight = it->second.height; + c->mDescription.vendorFlags = 0xFFFFFFFF; // Arbitrary test value + + c->mFormat = HAL_PIXEL_FORMAT_RGBA_8888; + c->mUsage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE | + GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY; + + return c; +} + +} // namespace aidl::android::hardware::automotive::evs::implementation diff --git a/automotive/evs/aidl/impl/default/src/GlWrapper.cpp b/automotive/evs/aidl/impl/default/src/GlWrapper.cpp new file mode 100644 index 0000000000..0ee5ecb734 --- /dev/null +++ b/automotive/evs/aidl/impl/default/src/GlWrapper.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GlWrapper.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace { + +using ::aidl::android::frameworks::automotive::display::DisplayDesc; +using ::aidl::android::frameworks::automotive::display::ICarDisplayProxy; +using ::aidl::android::frameworks::automotive::display::Rotation; +using ::aidl::android::hardware::common::NativeHandle; +using ::aidl::android::hardware::graphics::common::HardwareBufferDescription; +using ::android::GraphicBuffer; +using ::android::sp; + +constexpr const char vertexShaderSource[] = + "attribute vec4 pos; \n" + "attribute vec2 tex; \n" + "varying vec2 uv; \n" + "void main() \n" + "{ \n" + " gl_Position = pos; \n" + " uv = tex; \n" + "} \n"; + +constexpr const char pixelShaderSource[] = + "precision mediump float; \n" + "uniform sampler2D tex; \n" + "varying vec2 uv; \n" + "void main() \n" + "{ \n" + " gl_FragColor = texture2D(tex, uv);\n" + "} \n"; + +const char* getEGLError(void) { + switch (eglGetError()) { + case EGL_SUCCESS: + return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + default: + return "Unknown error"; + } +} + +// Given shader source, load and compile it +GLuint loadShader(GLenum type, const char* shaderSrc) { + // Create the shader object + GLuint shader = glCreateShader(type); + if (shader == 0) { + return 0; + } + + // Load and compile the shader + glShaderSource(shader, 1, &shaderSrc, nullptr); + glCompileShader(shader); + + // Verify the compilation worked as expected + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + LOG(ERROR) << "Error compiling shader"; + + GLint size = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size); + if (size > 0) { + // Get and report the error message + char* infoLog = (char*)malloc(size); + glGetShaderInfoLog(shader, size, nullptr, infoLog); + LOG(ERROR) << " msg:" << std::endl << infoLog; + free(infoLog); + } + + glDeleteShader(shader); + return 0; + } + + return shader; +} + +// Create a program object given vertex and pixels shader source +GLuint buildShaderProgram(const char* vtxSrc, const char* pxlSrc) { + GLuint program = glCreateProgram(); + if (program == 0) { + LOG(ERROR) << "Failed to allocate program object"; + return 0; + } + + // Compile the shaders and bind them to this program + GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vtxSrc); + if (vertexShader == 0) { + LOG(ERROR) << "Failed to load vertex shader"; + glDeleteProgram(program); + return 0; + } + GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pxlSrc); + if (pixelShader == 0) { + LOG(ERROR) << "Failed to load pixel shader"; + glDeleteProgram(program); + glDeleteShader(vertexShader); + return 0; + } + glAttachShader(program, vertexShader); + glAttachShader(program, pixelShader); + + glBindAttribLocation(program, 0, "pos"); + glBindAttribLocation(program, 1, "tex"); + + // Link the program + glLinkProgram(program); + GLint linked = 0; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (!linked) { + LOG(ERROR) << "Error linking program"; + GLint size = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &size); + if (size > 0) { + // Get and report the error message + char* infoLog = (char*)malloc(size); + glGetProgramInfoLog(program, size, nullptr, infoLog); + LOG(ERROR) << " msg: " << infoLog; + free(infoLog); + } + + glDeleteProgram(program); + glDeleteShader(vertexShader); + glDeleteShader(pixelShader); + return 0; + } + + return program; +} + +::android::sp convertNativeHandleToHGBP(const NativeHandle& aidlHandle) { + native_handle_t* handle = ::android::dupFromAidl(aidlHandle); + if (handle->numFds != 0 || handle->numInts < std::ceil(sizeof(size_t) / sizeof(int))) { + LOG(ERROR) << "Invalid native handle"; + return nullptr; + } + ::android::hardware::hidl_vec halToken; + halToken.setToExternal(reinterpret_cast(const_cast(&(handle->data[1]))), + handle->data[0]); + ::android::sp hgbp = + HGraphicBufferProducer::castFrom(::android::retrieveHalInterface(halToken)); + return std::move(hgbp); +} + +} // namespace + +namespace aidl::android::hardware::automotive::evs::implementation { + +// Main entry point +bool GlWrapper::initialize(const std::shared_ptr& pWindowProxy, + uint64_t displayId) { + LOG(DEBUG) << __FUNCTION__; + + if (!pWindowProxy) { + LOG(ERROR) << "Could not get ICarDisplayProxy."; + return false; + } + + DisplayDesc displayDesc; + auto status = pWindowProxy->getDisplayInfo(displayId, &displayDesc); + if (!status.isOk()) { + LOG(ERROR) << "Failed to read the display information"; + return false; + } + + mWidth = displayDesc.width; + mHeight = displayDesc.height; + if ((displayDesc.orientation != Rotation::ROTATION_0) && + (displayDesc.orientation != Rotation::ROTATION_180)) { + std::swap(mWidth, mHeight); + } + LOG(INFO) << "Display resolution is " << mWidth << "x" << mHeight; + + NativeHandle aidlHandle; + status = pWindowProxy->getHGraphicBufferProducer(displayId, &aidlHandle); + if (!status.isOk()) { + LOG(ERROR) << "Failed to get IGraphicBufferProducer from ICarDisplayProxy."; + return false; + } + + mGfxBufferProducer = convertNativeHandleToHGBP(aidlHandle); + if (!mGfxBufferProducer) { + LOG(ERROR) << "Failed to convert a NativeHandle to HGBP."; + return false; + } + + mSurfaceHolder = getSurfaceFromHGBP(mGfxBufferProducer); + if (mSurfaceHolder == nullptr) { + LOG(ERROR) << "Failed to get a Surface from HGBP."; + return false; + } + + mWindow = getNativeWindow(mSurfaceHolder.get()); + if (mWindow == nullptr) { + LOG(ERROR) << "Failed to get a native window from Surface."; + return false; + } + + // Set up our OpenGL ES context associated with the default display + mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mDisplay == EGL_NO_DISPLAY) { + LOG(ERROR) << "Failed to get egl display"; + return false; + } + + EGLint major = 2; + EGLint minor = 0; + if (!eglInitialize(mDisplay, &major, &minor)) { + LOG(ERROR) << "Failed to initialize EGL: " << getEGLError(); + return false; + } + + const EGLint config_attribs[] = { + // clang-format off + // Tag Value + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_NONE + // clang-format on + }; + + // Pick the default configuration without constraints (is this good enough?) + EGLConfig egl_config = {0}; + EGLint numConfigs = -1; + eglChooseConfig(mDisplay, config_attribs, &egl_config, 1, &numConfigs); + if (numConfigs != 1) { + LOG(ERROR) << "Didn't find a suitable format for our display window, " << getEGLError(); + return false; + } + + // Create the EGL render target surface + mSurface = eglCreateWindowSurface(mDisplay, egl_config, mWindow, nullptr); + if (mSurface == EGL_NO_SURFACE) { + LOG(ERROR) << "eglCreateWindowSurface failed, " << getEGLError(); + return false; + } + + // Create the EGL context + // NOTE: Our shader is (currently at least) written to require version 3, so this + // is required. + const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + mContext = eglCreateContext(mDisplay, egl_config, EGL_NO_CONTEXT, context_attribs); + if (mContext == EGL_NO_CONTEXT) { + LOG(ERROR) << "Failed to create OpenGL ES Context: " << getEGLError(); + return false; + } + + // Activate our render target for drawing + if (!eglMakeCurrent(mDisplay, mSurface, mSurface, mContext)) { + LOG(ERROR) << "Failed to make the OpenGL ES Context current: " << getEGLError(); + return false; + } + + // Create the shader program for our simple pipeline + mShaderProgram = buildShaderProgram(vertexShaderSource, pixelShaderSource); + if (!mShaderProgram) { + LOG(ERROR) << "Failed to build shader program: " << getEGLError(); + return false; + } + + // Create a GL texture that will eventually wrap our externally created texture surface(s) + glGenTextures(1, &mTextureMap); + if (mTextureMap <= 0) { + LOG(ERROR) << "Didn't get a texture handle allocated: " << getEGLError(); + return false; + } + + // Turn off mip-mapping for the created texture surface + // (the inbound camera imagery doesn't have MIPs) + glBindTexture(GL_TEXTURE_2D, mTextureMap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + return true; +} + +void GlWrapper::shutdown() { + // Drop our device textures + if (mKHRimage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mDisplay, mKHRimage); + mKHRimage = EGL_NO_IMAGE_KHR; + } + + // Release all GL resources + if (eglGetCurrentContext() == mContext) { + eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + eglDestroySurface(mDisplay, mSurface); + eglDestroyContext(mDisplay, mContext); + eglTerminate(mDisplay); + mSurface = EGL_NO_SURFACE; + mContext = EGL_NO_CONTEXT; + mDisplay = EGL_NO_DISPLAY; + + // Release the window + mSurfaceHolder = nullptr; +} + +void GlWrapper::showWindow(const std::shared_ptr& pWindowProxy, uint64_t id) { + if (pWindowProxy) { + pWindowProxy->showWindow(id); + } else { + LOG(ERROR) << "ICarDisplayProxy is not available."; + } +} + +void GlWrapper::hideWindow(const std::shared_ptr& pWindowProxy, uint64_t id) { + if (pWindowProxy) { + pWindowProxy->hideWindow(id); + } else { + LOG(ERROR) << "ICarDisplayProxy is not available."; + } +} + +bool GlWrapper::updateImageTexture(buffer_handle_t handle, + const HardwareBufferDescription& description) { + if (mKHRimage != EGL_NO_IMAGE_KHR) { + return true; + } + + // Create a temporary GraphicBuffer to wrap the provided handle. + sp pGfxBuffer = + new GraphicBuffer(description.width, description.height, + static_cast<::android::PixelFormat>(description.format), + description.layers, static_cast(description.usage), + description.stride, const_cast(handle), + /* keepOwnership= */ false); + if (!pGfxBuffer) { + LOG(ERROR) << "Failed to allocate GraphicBuffer to wrap our native handle"; + return false; + } + + // Get a GL compatible reference to the graphics buffer we've been given + EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; + EGLClientBuffer cbuf = static_cast(pGfxBuffer->getNativeBuffer()); + mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, cbuf, + eglImageAttributes); + if (mKHRimage == EGL_NO_IMAGE_KHR) { + LOG(ERROR) << "Error creating EGLImage: " << getEGLError(); + return false; + } + + // Update the texture handle we already created to refer to this gralloc buffer + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, mTextureMap); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast(mKHRimage)); + + return true; +} + +void GlWrapper::renderImageToScreen() { + // Set the viewport + glViewport(0, 0, mWidth, mHeight); + + // Clear the color buffer + glClearColor(0.1f, 0.5f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Select our screen space simple texture shader + glUseProgram(mShaderProgram); + + // Bind the texture and assign it to the shader's sampler + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, mTextureMap); + GLint sampler = glGetUniformLocation(mShaderProgram, "tex"); + glUniform1i(sampler, 0); + + // We want our image to show up opaque regardless of alpha values + glDisable(GL_BLEND); + + // Draw a rectangle on the screen + GLfloat vertsCarPos[] = { + // clang-format off + -0.8, 0.8, 0.0f, // left top in window space + 0.8, 0.8, 0.0f, // right top + -0.8, -0.8, 0.0f, // left bottom + 0.8, -0.8, 0.0f // right bottom + // clang-format on + }; + + // NOTE: We didn't flip the image in the texture, so V=0 is actually the top of the image + GLfloat vertsCarTex[] = { + // clang-format off + 0.0f, 0.0f, // left top + 1.0f, 0.0f, // right top + 0.0f, 1.0f, // left bottom + 1.0f, 1.0f // right bottom + // clang-format on + }; + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Clean up and flip the rendered result to the front so it is visible + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + + glFinish(); + + if (eglSwapBuffers(mDisplay, mSurface) == EGL_FALSE) { + LOG(WARNING) << "Failed to swap EGL buffers, " << getEGLError(); + } +} + +} // namespace aidl::android::hardware::automotive::evs::implementation diff --git a/automotive/evs/aidl/impl/default/src/service.cpp b/automotive/evs/aidl/impl/default/src/service.cpp index 0a0913fd3c..7532d8735f 100644 --- a/automotive/evs/aidl/impl/default/src/service.cpp +++ b/automotive/evs/aidl/impl/default/src/service.cpp @@ -14,38 +14,75 @@ * limitations under the License. */ -#define LOG_TAG "EvsService" - -#include +#include "EvsEnumerator.h" +#include "EvsGlDisplay.h" #include #include #include -using ::aidl::android::hardware::automotive::evs::implementation::DefaultEvsEnumerator; +#include -int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { - std::shared_ptr vhal = ndk::SharedRefBase::make(); +#include +#include +#include - ALOGI("Registering as service..."); - binder_exception_t err = - AServiceManager_addService(vhal->asBinder().get(), "android.hardware.automotive.evs"); +namespace { + +using ::aidl::android::frameworks::automotive::display::ICarDisplayProxy; +using ::aidl::android::hardware::automotive::evs::implementation::EvsEnumerator; + +constexpr std::string_view kDisplayServiceInstanceName = "/default"; +constexpr std::string_view kHwInstanceName = "/hw/0"; +constexpr int kNumBinderThreads = 1; + +} // namespace + +int main() { + LOG(INFO) << "EVS Hardware Enumerator service is starting"; + + const std::string displayServiceInstanceName = + std::string(ICarDisplayProxy::descriptor) + std::string(kDisplayServiceInstanceName); + if (!AServiceManager_isDeclared(displayServiceInstanceName.data())) { + // TODO: We may just want to disable EVS display. + LOG(ERROR) << displayServiceInstanceName << " is required."; + return EXIT_FAILURE; + } + + std::shared_ptr displayService = ICarDisplayProxy::fromBinder( + ::ndk::SpAIBinder(AServiceManager_waitForService(displayServiceInstanceName.data()))); + if (!displayService) { + LOG(ERROR) << "Cannot use " << displayServiceInstanceName << ". Exiting."; + return EXIT_FAILURE; + } + + // Register our service -- if somebody is already registered by our name, + // they will be killed (their thread pool will throw an exception). + std::shared_ptr service = + ndk::SharedRefBase::make(displayService); + if (!service) { + LOG(ERROR) << "Failed to instantiate the service"; + return EXIT_FAILURE; + } + + const std::string instanceName = + std::string(EvsEnumerator::descriptor) + std::string(kHwInstanceName); + auto err = AServiceManager_addService(service->asBinder().get(), instanceName.data()); if (err != EX_NONE) { - ALOGE("failed to register android.hardware.automotive.evs service, exception: %d", err); - return 1; + LOG(ERROR) << "Failed to register " << instanceName << ", exception = " << err; + return EXIT_FAILURE; } - if (!ABinderProcess_setThreadPoolMaxThreadCount(1)) { - ALOGE("%s", "failed to set thread pool max thread count"); - return 1; + if (!ABinderProcess_setThreadPoolMaxThreadCount(kNumBinderThreads)) { + LOG(ERROR) << "Failed to set thread pool"; + return EXIT_FAILURE; } + ABinderProcess_startThreadPool(); - - ALOGI("Evs Service Ready"); + LOG(INFO) << "EVS Hardware Enumerator is ready"; ABinderProcess_joinThreadPool(); - - ALOGI("Evs Service Exiting"); - - return 0; + // In normal operation, we don't expect the thread pool to exit + LOG(INFO) << "EVS Hardware Enumerator is shutting down"; + return EXIT_SUCCESS; } diff --git a/compatibility_matrices/compatibility_matrix.8.xml b/compatibility_matrices/compatibility_matrix.8.xml index d4fca6a85e..8f16e06a84 100644 --- a/compatibility_matrices/compatibility_matrix.8.xml +++ b/compatibility_matrices/compatibility_matrix.8.xml @@ -84,15 +84,6 @@ [a-z]+/[0-9]+ - - android.hardware.automotive.evs - 1.0-1 - - IEvsEnumerator - default - [a-z]+/[0-9]+ - - android.hardware.automotive.occupant_awareness 1