471 lines
17 KiB
C++
471 lines
17 KiB
C++
/*
|
|
* Copyright 2021 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 "EvsServiceContext.h"
|
|
|
|
#include <aidl/android/hardware/automotive/evs/EvsResult.h>
|
|
#include <aidl/android/hardware/common/NativeHandle.h>
|
|
#include <aidl/android/hardware/graphics/common/HardwareBuffer.h>
|
|
#include <aidl/android/hardware/graphics/common/PixelFormat.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/scopeguard.h>
|
|
#include <android/binder_ibinder.h>
|
|
#include <android/binder_manager.h>
|
|
#include <android/hardware_buffer_jni.h> // for AHardwareBuffer_toHardwareBuffer
|
|
#include <cutils/native_handle.h>
|
|
#include <nativehelper/JNIHelp.h>
|
|
#include <vndk/hardware_buffer.h> // for AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE
|
|
|
|
namespace {
|
|
|
|
using ::aidl::android::hardware::automotive::evs::BufferDesc;
|
|
using ::aidl::android::hardware::automotive::evs::CameraDesc;
|
|
using ::aidl::android::hardware::automotive::evs::EvsEventDesc;
|
|
using ::aidl::android::hardware::automotive::evs::EvsResult;
|
|
using ::aidl::android::hardware::automotive::evs::IEvsCamera;
|
|
using ::aidl::android::hardware::automotive::evs::IEvsEnumerator;
|
|
using ::aidl::android::hardware::automotive::evs::Stream;
|
|
using ::aidl::android::hardware::automotive::evs::StreamType;
|
|
using ::aidl::android::hardware::common::NativeHandle;
|
|
using ::aidl::android::hardware::graphics::common::HardwareBuffer;
|
|
using AidlPixelFormat = ::aidl::android::hardware::graphics::common::PixelFormat;
|
|
|
|
jmethodID getMethodIDOrDie(JNIEnv* env, jclass clazz, const char* name, const char* signature) {
|
|
jmethodID res = env->GetMethodID(clazz, name, signature);
|
|
if (res == nullptr) {
|
|
LOG(FATAL) << "Unable to find method " << name << " with signature = " << signature;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Stream selectStreamConfiguration(const std::vector<Stream>& list) {
|
|
for (const auto& cfg : list) {
|
|
// TODO(b/223905367): this logic simply selects the first output stream
|
|
// configuration that generates RGBA8888 data stream.
|
|
if (cfg.streamType == StreamType::OUTPUT && cfg.format == AidlPixelFormat::RGBA_8888) {
|
|
LOG(INFO) << "Selected stream configuration: width = " << cfg.width
|
|
<< ", height = " << cfg.height
|
|
<< ", format = " << static_cast<int>(cfg.format);
|
|
return std::move(cfg);
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
native_handle_t* makeFromAidl(const NativeHandle& handle) {
|
|
// Create native_handle_t from
|
|
// ::aidl::android::hardware::common::NativeHandle. See also
|
|
// ::android::makeFromAidl() and native_handle_create().
|
|
const auto numFds = handle.fds.size();
|
|
const auto numInts = handle.ints.size();
|
|
|
|
if (numFds < 0 || numInts < 0 || numFds > NATIVE_HANDLE_MAX_FDS ||
|
|
numInts > NATIVE_HANDLE_MAX_INTS) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto mallocSize = sizeof(native_handle_t) + (sizeof(int) * (numFds + numInts));
|
|
native_handle_t* h = static_cast<native_handle_t*>(malloc(mallocSize));
|
|
if (h == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
h->version = sizeof(native_handle_t);
|
|
h->numFds = numFds;
|
|
h->numInts = numInts;
|
|
for (auto i = 0; i < handle.fds.size(); ++i) {
|
|
h->data[i] = handle.fds[i].get();
|
|
}
|
|
memcpy(h->data + handle.fds.size(), handle.ints.data(), handle.ints.size() * sizeof(int));
|
|
|
|
return h;
|
|
}
|
|
|
|
// "default" is reserved for the latest version of EVS manager.
|
|
constexpr const char kEvsManagerServiceName[] =
|
|
"android.hardware.automotive.evs.IEvsEnumerator/default";
|
|
|
|
} // namespace
|
|
|
|
namespace android::automotive::evs {
|
|
|
|
EvsServiceContext::EvsServiceContext(JavaVM* vm, jclass clazz) :
|
|
mVm(vm), mCallbackThread(vm), mCarEvsServiceObj(nullptr) {
|
|
JNIEnv* env = nullptr;
|
|
vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4);
|
|
if (env != nullptr) {
|
|
// Registers post-native handlers
|
|
mDeathHandlerMethodId = getMethodIDOrDie(env, clazz, "postNativeDeathHandler", "()V");
|
|
mEventHandlerMethodId = getMethodIDOrDie(env, clazz, "postNativeEventHandler", "(I)V");
|
|
mFrameHandlerMethodId = getMethodIDOrDie(env, clazz, "postNativeFrameHandler",
|
|
"(ILandroid/hardware/HardwareBuffer;)V");
|
|
} else {
|
|
jniThrowException(env, "java/lang/IllegalArgumentException",
|
|
"Failed to get JNIEnv from a given VM instance.");
|
|
}
|
|
}
|
|
|
|
EvsServiceContext::~EvsServiceContext() {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
if (mService) {
|
|
::AIBinder_DeathRecipient_delete(mDeathRecipient.get());
|
|
}
|
|
mService = nullptr;
|
|
mCamera = nullptr;
|
|
mStreamHandler = nullptr;
|
|
}
|
|
|
|
// Stops the callback thread
|
|
mCallbackThread.stop();
|
|
|
|
// Deletes a global reference to the CarEvsService object
|
|
JNIEnv* env = nullptr;
|
|
if (mVm != nullptr) {
|
|
mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4);
|
|
if (env != nullptr) {
|
|
env->DeleteGlobalRef(mCarEvsServiceObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EvsServiceContext::initialize(JNIEnv* env, jobject thiz) {
|
|
bool isDeclared = ::AServiceManager_isDeclared(kEvsManagerServiceName);
|
|
if (!isDeclared) {
|
|
LOG(ERROR) << kEvsManagerServiceName << " is not available.";
|
|
return false;
|
|
}
|
|
|
|
AIBinder* binder = ::AServiceManager_checkService(kEvsManagerServiceName);
|
|
if (binder == nullptr) {
|
|
LOG(ERROR) << "IEvsEnumerator is not ready yet.";
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<IEvsEnumerator> service = IEvsEnumerator::fromBinder(::ndk::SpAIBinder(binder));
|
|
if (!service) {
|
|
LOG(ERROR) << "Failed to connect to EVS service.";
|
|
return false;
|
|
}
|
|
|
|
auto deathRecipient = ::AIBinder_DeathRecipient_new(EvsServiceContext::onEvsServiceBinderDied);
|
|
auto status = ::ndk::ScopedAStatus::fromStatus(
|
|
::AIBinder_linkToDeath(service->asBinder().get(), deathRecipient, this));
|
|
if (!status.isOk()) {
|
|
LOG(WARNING) << "Failed to register a death recipient; continuing anyway: "
|
|
<< status.getMessage();
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
mService = service;
|
|
mDeathRecipient = ::ndk::ScopedAIBinder_DeathRecipient(deathRecipient);
|
|
if (!mCarEvsServiceObj) {
|
|
mCarEvsServiceObj = env->NewGlobalRef(thiz);
|
|
}
|
|
|
|
// Reset a stored camera id and a display handle
|
|
mCameraIdInUse.clear();
|
|
mDisplay = nullptr;
|
|
}
|
|
|
|
// Fetch a list of available camera devices
|
|
status = service->getCameraList(&mCameraList);
|
|
if (!status.isOk()) {
|
|
LOG(ERROR) << "Failed to load a camera list, error = " << status.getServiceSpecificError();
|
|
return false;
|
|
} else if (mCameraList.size() < 1) {
|
|
LOG(ERROR) << "No camera device is available";
|
|
return false;
|
|
}
|
|
|
|
LOG(INFO) << mCameraList.size() << " camera devices are listed.";
|
|
return true;
|
|
}
|
|
|
|
bool EvsServiceContext::openCamera(const char* id) {
|
|
if (!isAvailable()) {
|
|
LOG(ERROR) << "Has not connected to EVS service yet.";
|
|
return false;
|
|
}
|
|
|
|
if (isCameraOpened()) {
|
|
if (mCameraIdInUse == id) {
|
|
LOG(DEBUG) << "Camera " << id << " is has opened already.";
|
|
return true;
|
|
} else {
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
if (mService) {
|
|
// Close a current camera device.
|
|
if (!mService->closeCamera(mCamera).isOk()) {
|
|
LOG(WARNING) << "Failed to close a current camera device";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto it = std::find_if(mCameraList.begin(), mCameraList.end(),
|
|
[target = std::string(id)](const CameraDesc& desc) {
|
|
return target == desc.id;
|
|
});
|
|
if (it == mCameraList.end()) {
|
|
LOG(ERROR) << id << " is not available";
|
|
return false;
|
|
}
|
|
|
|
std::vector<Stream> availableStreams;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
if (!mService) {
|
|
return false;
|
|
}
|
|
mService->getStreamList(*it, &availableStreams);
|
|
|
|
Stream streamConfig = selectStreamConfiguration(availableStreams);
|
|
std::shared_ptr<IEvsCamera> camObj;
|
|
if (!mService || !mService->openCamera(id, streamConfig, &camObj).isOk() || !camObj) {
|
|
LOG(ERROR) << "Failed to open a camera " << id;
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<StreamHandler> streamHandler =
|
|
::ndk::SharedRefBase::make<StreamHandler>(camObj, this,
|
|
EvsServiceContext::kMaxNumFramesInFlight);
|
|
if (!streamHandler) {
|
|
LOG(ERROR) << "Failed to initialize a stream streamHandler.";
|
|
if (!mService->closeCamera(camObj).isOk()) {
|
|
LOG(ERROR) << "Failed to close a temporary camera device";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
mCamera = std::move(camObj);
|
|
mStreamHandler = std::move(streamHandler);
|
|
mCameraIdInUse = id;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void EvsServiceContext::closeCamera() {
|
|
if (!isCameraOpened()) {
|
|
LOG(DEBUG) << "Camera has not opened yet.";
|
|
return;
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
if (!mService->closeCamera(mCamera).isOk()) {
|
|
LOG(WARNING) << "Failed to close a current camera device.";
|
|
}
|
|
}
|
|
|
|
// Reset a camera reference and id in use.
|
|
mCamera.reset();
|
|
mCameraIdInUse.clear();
|
|
}
|
|
|
|
bool EvsServiceContext::startVideoStream() {
|
|
if (!isCameraOpened()) {
|
|
LOG(ERROR) << "Camera has not opened yet.";
|
|
return JNI_FALSE;
|
|
}
|
|
|
|
return mStreamHandler->startStream();
|
|
}
|
|
|
|
void EvsServiceContext::stopVideoStream() {
|
|
if (!isCameraOpened()) {
|
|
LOG(DEBUG) << "Camera has not opened; a request to stop a video steram is ignored.";
|
|
return;
|
|
}
|
|
|
|
if (!mStreamHandler->asyncStopStream()) {
|
|
LOG(WARNING) << "Failed to stop a video stream. EVS service may die.";
|
|
}
|
|
}
|
|
|
|
void EvsServiceContext::acquireCameraAndDisplayLocked() {
|
|
if (!mCamera) {
|
|
LOG(DEBUG) << "A target camera is not available.";
|
|
return;
|
|
}
|
|
|
|
// Acquires the display ownership. Because EVS awards this to the single
|
|
// client, no other clients can use EvsDisplay as long as CarEvsManager
|
|
// alives.
|
|
::ndk::ScopedAStatus status =
|
|
mService->openDisplay(EvsServiceContext::kExclusiveMainDisplayId, &mDisplay);
|
|
if (!status.isOk() || !mDisplay) {
|
|
LOG(WARNING) << "Failed to acquire the display ownership. "
|
|
<< "CarEvsManager may not be able to render "
|
|
<< "the contents on the screen.";
|
|
return;
|
|
}
|
|
|
|
// Attempts to become a primary owner
|
|
status = mCamera->forcePrimaryClient(mDisplay);
|
|
if (!status.isOk() ||
|
|
static_cast<EvsResult>(status.getServiceSpecificError()) != EvsResult::OK) {
|
|
LOG(WARNING) << "Failed to own a camera device: " << status.getMessage();
|
|
}
|
|
}
|
|
|
|
void EvsServiceContext::doneWithFrame(int bufferId) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
if (!mStreamHandler) {
|
|
LOG(DEBUG) << "A stream handler is not available.";
|
|
return;
|
|
}
|
|
|
|
auto it = mBufferRecords.find(bufferId);
|
|
if (it == mBufferRecords.end()) {
|
|
LOG(WARNING) << "Unknown buffer is requested to return.";
|
|
return;
|
|
}
|
|
|
|
mBufferRecords.erase(it);
|
|
|
|
// If this is the first frame since current video stream started, we'd claim
|
|
// the exclusive ownership of the camera and the display and keep for the rest
|
|
// of the lifespan.
|
|
if (!mDisplay) {
|
|
acquireCameraAndDisplayLocked();
|
|
}
|
|
}
|
|
mStreamHandler->doneWithFrame(bufferId);
|
|
}
|
|
|
|
/*
|
|
* Forwards EVS stream events to the client. This method will run in the
|
|
* context of EvsCallbackThread.
|
|
*/
|
|
void EvsServiceContext::onNewEvent(const EvsEventDesc& event) {
|
|
mCallbackThread.enqueue([event, this](JNIEnv* env) {
|
|
// Gives an event callback
|
|
env->CallVoidMethod(mCarEvsServiceObj, mEventHandlerMethodId,
|
|
static_cast<jint>(event.aType));
|
|
});
|
|
}
|
|
|
|
/*
|
|
* Forwards EVS frames to the client. This method will run in the context of
|
|
* EvsCallbackThread.
|
|
*/
|
|
bool EvsServiceContext::onNewFrame(const BufferDesc& bufferDesc) {
|
|
// Create AHardwareBuffer from ::aidl::android::hardware::automotive::evs::BufferDesc
|
|
native_handle_t* nativeHandle = makeFromAidl(bufferDesc.buffer.handle);
|
|
const auto handleGuard = ::android::base::make_scope_guard([nativeHandle] {
|
|
// We only need to free an allocated memory because a source buffer is
|
|
// owned by EVS HAL implementation.
|
|
free(nativeHandle);
|
|
});
|
|
|
|
if (nativeHandle == nullptr ||
|
|
!std::all_of(nativeHandle->data + 0, nativeHandle->data + nativeHandle->numFds,
|
|
[](int fd) { return fd >= 0; })) {
|
|
LOG(ERROR) << " android::makeFromAidl returned an invalid native handle";
|
|
return false;
|
|
}
|
|
|
|
const AHardwareBuffer_Desc desc{
|
|
.width = static_cast<uint32_t>(bufferDesc.buffer.description.width),
|
|
.height = static_cast<uint32_t>(bufferDesc.buffer.description.height),
|
|
.layers = static_cast<uint32_t>(bufferDesc.buffer.description.layers),
|
|
.format = static_cast<uint32_t>(bufferDesc.buffer.description.format),
|
|
.usage = static_cast<uint64_t>(bufferDesc.buffer.description.usage),
|
|
.stride = static_cast<uint32_t>(bufferDesc.buffer.description.stride),
|
|
};
|
|
|
|
AHardwareBuffer* ahwb = nullptr;
|
|
const auto status =
|
|
AHardwareBuffer_createFromHandle(&desc, nativeHandle,
|
|
AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE,
|
|
&ahwb);
|
|
if (status != android::NO_ERROR) {
|
|
LOG(ERROR) << "Failed to create a raw hardware buffer from a native handle, "
|
|
<< "status = " << statusToString(status);
|
|
mStreamHandler->doneWithFrame(bufferDesc.bufferId);
|
|
return false;
|
|
}
|
|
|
|
mCallbackThread.enqueue([ahwb, bufferId = bufferDesc.bufferId, this](JNIEnv* env) {
|
|
{
|
|
std::lock_guard lock(mLock);
|
|
mBufferRecords.insert(bufferId);
|
|
}
|
|
|
|
// Forward AHardwareBuffer to the client
|
|
jobject hwBuffer = AHardwareBuffer_toHardwareBuffer(env, ahwb);
|
|
if (!hwBuffer) {
|
|
LOG(WARNING) << "Failed to create HardwareBuffer from AHardwareBuffer.";
|
|
mStreamHandler->doneWithFrame(bufferId);
|
|
} else {
|
|
env->CallVoidMethod(mCarEvsServiceObj, mFrameHandlerMethodId, bufferId, hwBuffer);
|
|
env->DeleteLocalRef(hwBuffer);
|
|
}
|
|
|
|
// We're done
|
|
AHardwareBuffer_release(ahwb);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Handles an unexpected death of EVS service. This method will run in the
|
|
* context of EvsCallbackThread.
|
|
*/
|
|
void EvsServiceContext::onEvsServiceDiedImpl() {
|
|
mCallbackThread.enqueue([this](JNIEnv* env) {
|
|
// Drops invalidated service handles. We will re-initialize them when
|
|
// we try to reconnect. The buffer record would be cleared safely
|
|
// because all buffer references get invalidated upon the death of the
|
|
// native EVS service.
|
|
{
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
mCamera = nullptr;
|
|
mService = nullptr;
|
|
mStreamHandler = nullptr;
|
|
mBufferRecords.clear();
|
|
mCameraIdInUse.clear();
|
|
}
|
|
|
|
LOG(ERROR) << "The native EVS service has died.";
|
|
// EVS service has died but CarEvsManager instance still alives.
|
|
// Only a service handle needs to be destroyed; this will be
|
|
// re-created when CarEvsManager successfully connects to EVS service
|
|
// when it comes back.
|
|
env->CallVoidMethod(mCarEvsServiceObj, mDeathHandlerMethodId);
|
|
});
|
|
}
|
|
|
|
void EvsServiceContext::onEvsServiceBinderDied(void* cookie) {
|
|
auto thiz = static_cast<EvsServiceContext*>(cookie);
|
|
if (!thiz) {
|
|
LOG(WARNING) << "A death of the EVS service is detected but ignored "
|
|
<< "because of the invalid service context.";
|
|
return;
|
|
}
|
|
|
|
thiz->onEvsServiceDiedImpl();
|
|
}
|
|
|
|
} // namespace android::automotive::evs
|