You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
16 KiB
402 lines
16 KiB
4 months ago
|
/*
|
||
|
* Copyright (C) 2016 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 "EvsStateControl.h"
|
||
|
|
||
|
#include "FormatConvert.h"
|
||
|
#include "RenderDirectView.h"
|
||
|
#include "RenderPixelCopy.h"
|
||
|
#include "RenderTopView.h"
|
||
|
|
||
|
#include <android-base/logging.h>
|
||
|
#include <android/binder_manager.h>
|
||
|
#include <utils/SystemClock.h>
|
||
|
|
||
|
#include <inttypes.h>
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
using ::android::hardware::automotive::evs::V1_0::EvsResult;
|
||
|
using EvsDisplayState = ::android::hardware::automotive::evs::V1_0::DisplayState;
|
||
|
using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
|
||
|
using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc;
|
||
|
|
||
|
static bool isSfReady() {
|
||
|
return ::ndk::SpAIBinder(::AServiceManager_getService("SurfaceFlinger")).get() != nullptr;
|
||
|
}
|
||
|
|
||
|
// TODO: Seems like it'd be nice if the Vehicle HAL provided such helpers (but how & where?)
|
||
|
inline constexpr VehiclePropertyType getPropType(VehicleProperty prop) {
|
||
|
return static_cast<VehiclePropertyType>(
|
||
|
static_cast<int32_t>(prop)
|
||
|
& static_cast<int32_t>(VehiclePropertyType::MASK));
|
||
|
}
|
||
|
|
||
|
EvsStateControl::EvsStateControl(android::sp<IVehicle> pVnet, android::sp<IEvsEnumerator> pEvs,
|
||
|
android::sp<IEvsDisplay> pDisplay, const ConfigManager& config) :
|
||
|
mVehicle(pVnet),
|
||
|
mEvs(pEvs),
|
||
|
mDisplay(pDisplay),
|
||
|
mConfig(config),
|
||
|
mCurrentState(OFF),
|
||
|
mEvsStats(EvsStats::build()) {
|
||
|
// Initialize the property value containers we'll be updating (they'll be zeroed by default)
|
||
|
static_assert(getPropType(VehicleProperty::GEAR_SELECTION) == VehiclePropertyType::INT32,
|
||
|
"Unexpected type for GEAR_SELECTION property");
|
||
|
static_assert(getPropType(VehicleProperty::TURN_SIGNAL_STATE) == VehiclePropertyType::INT32,
|
||
|
"Unexpected type for TURN_SIGNAL_STATE property");
|
||
|
|
||
|
mGearValue.prop = static_cast<int32_t>(VehicleProperty::GEAR_SELECTION);
|
||
|
mTurnSignalValue.prop = static_cast<int32_t>(VehicleProperty::TURN_SIGNAL_STATE);
|
||
|
|
||
|
// This way we only ever deal with cameras which exist in the system
|
||
|
// Build our set of cameras for the states we support
|
||
|
LOG(DEBUG) << "Requesting camera list";
|
||
|
mEvs->getCameraList_1_1(
|
||
|
[this, &config](hidl_vec<CameraDesc> cameraList) {
|
||
|
LOG(INFO) << "Camera list callback received " << cameraList.size() << "cameras.";
|
||
|
for (auto&& cam: cameraList) {
|
||
|
LOG(DEBUG) << "Found camera " << cam.v1.cameraId;
|
||
|
bool cameraConfigFound = false;
|
||
|
|
||
|
// Check our configuration for information about this camera
|
||
|
// Note that a camera can have a compound function string
|
||
|
// such that a camera can be "right/reverse" and be used for both.
|
||
|
// If more than one camera is listed for a given function, we'll
|
||
|
// list all of them and let the UX/rendering logic use one, some
|
||
|
// or all of them as appropriate.
|
||
|
for (auto&& info: config.getCameras()) {
|
||
|
if (cam.v1.cameraId == info.cameraId) {
|
||
|
// We found a match!
|
||
|
if (info.function.find("reverse") != std::string::npos) {
|
||
|
mCameraList[State::REVERSE].emplace_back(info);
|
||
|
mCameraDescList[State::REVERSE].emplace_back(cam);
|
||
|
}
|
||
|
if (info.function.find("right") != std::string::npos) {
|
||
|
mCameraList[State::RIGHT].emplace_back(info);
|
||
|
mCameraDescList[State::RIGHT].emplace_back(cam);
|
||
|
}
|
||
|
if (info.function.find("left") != std::string::npos) {
|
||
|
mCameraList[State::LEFT].emplace_back(info);
|
||
|
mCameraDescList[State::LEFT].emplace_back(cam);
|
||
|
}
|
||
|
if (info.function.find("park") != std::string::npos) {
|
||
|
mCameraList[State::PARKING].emplace_back(info);
|
||
|
mCameraDescList[State::PARKING].emplace_back(cam);
|
||
|
}
|
||
|
cameraConfigFound = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!cameraConfigFound) {
|
||
|
LOG(WARNING) << "No config information for hardware camera "
|
||
|
<< cam.v1.cameraId;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
|
||
|
LOG(DEBUG) << "State controller ready";
|
||
|
}
|
||
|
|
||
|
bool EvsStateControl::startUpdateLoop() {
|
||
|
// Create the thread and report success if it gets started
|
||
|
mRenderThread = std::thread([this](){ updateLoop(); });
|
||
|
return mRenderThread.joinable();
|
||
|
}
|
||
|
|
||
|
|
||
|
void EvsStateControl::terminateUpdateLoop() {
|
||
|
// Join a rendering thread
|
||
|
if (mRenderThread.joinable()) {
|
||
|
mRenderThread.join();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void EvsStateControl::postCommand(const Command& cmd, bool clear) {
|
||
|
// Push the command onto the queue watched by updateLoop
|
||
|
mLock.lock();
|
||
|
if (clear) {
|
||
|
std::queue<Command> emptyQueue;
|
||
|
std::swap(emptyQueue, mCommandQueue);
|
||
|
}
|
||
|
|
||
|
mCommandQueue.push(cmd);
|
||
|
mLock.unlock();
|
||
|
|
||
|
// Send a signal to wake updateLoop in case it is asleep
|
||
|
mWakeSignal.notify_all();
|
||
|
}
|
||
|
|
||
|
|
||
|
void EvsStateControl::updateLoop() {
|
||
|
LOG(DEBUG) << "Starting EvsStateControl update loop";
|
||
|
|
||
|
bool run = true;
|
||
|
while (run) {
|
||
|
// Process incoming commands
|
||
|
{
|
||
|
std::lock_guard <std::mutex> lock(mLock);
|
||
|
while (!mCommandQueue.empty()) {
|
||
|
const Command& cmd = mCommandQueue.front();
|
||
|
switch (cmd.operation) {
|
||
|
case Op::EXIT:
|
||
|
run = false;
|
||
|
break;
|
||
|
case Op::CHECK_VEHICLE_STATE:
|
||
|
// Just running selectStateForCurrentConditions below will take care of this
|
||
|
break;
|
||
|
case Op::TOUCH_EVENT:
|
||
|
// Implement this given the x/y location of the touch event
|
||
|
break;
|
||
|
}
|
||
|
mCommandQueue.pop();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Review vehicle state and choose an appropriate renderer
|
||
|
if (!selectStateForCurrentConditions()) {
|
||
|
LOG(ERROR) << "selectStateForCurrentConditions failed so we're going to die";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If we have an active renderer, give it a chance to draw
|
||
|
if (mCurrentRenderer) {
|
||
|
// Get the output buffer we'll use to display the imagery
|
||
|
BufferDesc_1_0 tgtBuffer = {};
|
||
|
mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc_1_0& buff) {
|
||
|
tgtBuffer = buff;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
if (tgtBuffer.memHandle == nullptr) {
|
||
|
LOG(ERROR) << "Didn't get requested output buffer -- skipping this frame.";
|
||
|
run = false;
|
||
|
} else {
|
||
|
// Generate our output image
|
||
|
if (!mCurrentRenderer->drawFrame(convertBufferDesc(tgtBuffer))) {
|
||
|
// If drawing failed, we want to exit quickly so an app restart can happen
|
||
|
run = false;
|
||
|
}
|
||
|
|
||
|
// Send the finished image back for display
|
||
|
mDisplay->returnTargetBufferForDisplay(tgtBuffer);
|
||
|
|
||
|
if (!mFirstFrameIsDisplayed) {
|
||
|
mFirstFrameIsDisplayed = true;
|
||
|
// returnTargetBufferForDisplay() is finished, the frame should be displayed
|
||
|
mEvsStats.finishComputingFirstFrameLatency(android::uptimeMillis());
|
||
|
}
|
||
|
}
|
||
|
} else if (run) {
|
||
|
// No active renderer, so sleep until somebody wakes us with another command
|
||
|
// or exit if we received EXIT command
|
||
|
std::unique_lock<std::mutex> lock(mLock);
|
||
|
mWakeSignal.wait(lock);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LOG(WARNING) << "EvsStateControl update loop ending";
|
||
|
|
||
|
if (mCurrentRenderer) {
|
||
|
// Deactive the renderer
|
||
|
mCurrentRenderer->deactivate();
|
||
|
}
|
||
|
|
||
|
// If `ICarTelemetry` service was not ready before, we need to try sending data again.
|
||
|
mEvsStats.sendCollectedDataBlocking();
|
||
|
|
||
|
printf("Shutting down app due to state control loop ending\n");
|
||
|
LOG(ERROR) << "Shutting down app due to state control loop ending";
|
||
|
}
|
||
|
|
||
|
|
||
|
bool EvsStateControl::selectStateForCurrentConditions() {
|
||
|
static int32_t sMockGear = mConfig.getMockGearSignal();
|
||
|
static int32_t sMockSignal = int32_t(VehicleTurnSignal::NONE);
|
||
|
|
||
|
if (mVehicle != nullptr) {
|
||
|
// Query the car state
|
||
|
if (invokeGet(&mGearValue) != StatusCode::OK) {
|
||
|
LOG(ERROR) << "GEAR_SELECTION not available from vehicle. Exiting.";
|
||
|
return false;
|
||
|
}
|
||
|
if ((mTurnSignalValue.prop == 0) || (invokeGet(&mTurnSignalValue) != StatusCode::OK)) {
|
||
|
// Silently treat missing turn signal state as no turn signal active
|
||
|
mTurnSignalValue.value.int32Values.setToExternal(&sMockSignal, 1);
|
||
|
mTurnSignalValue.prop = 0;
|
||
|
}
|
||
|
} else {
|
||
|
// While testing without a vehicle, behave as if we're in reverse for the first 20 seconds
|
||
|
static const int kShowTime = 20; // seconds
|
||
|
|
||
|
// See if it's time to turn off the default reverse camera
|
||
|
static std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
||
|
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||
|
if (std::chrono::duration_cast<std::chrono::seconds>(now - start).count() > kShowTime) {
|
||
|
// Switch to drive (which should turn off the reverse camera)
|
||
|
sMockGear = int32_t(VehicleGear::GEAR_DRIVE);
|
||
|
}
|
||
|
|
||
|
// Build the placeholder vehicle state values (treating single values as 1 element vectors)
|
||
|
mGearValue.value.int32Values.setToExternal(&sMockGear, 1);
|
||
|
mTurnSignalValue.value.int32Values.setToExternal(&sMockSignal, 1);
|
||
|
}
|
||
|
|
||
|
// Choose our desired EVS state based on the current car state
|
||
|
// TODO: Update this logic, and consider user input when choosing if a view should be presented
|
||
|
State desiredState = OFF;
|
||
|
if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_REVERSE)) {
|
||
|
desiredState = REVERSE;
|
||
|
} else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::RIGHT)) {
|
||
|
desiredState = RIGHT;
|
||
|
} else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::LEFT)) {
|
||
|
desiredState = LEFT;
|
||
|
} else if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_PARK)) {
|
||
|
desiredState = PARKING;
|
||
|
}
|
||
|
|
||
|
// Apply the desire state
|
||
|
return configureEvsPipeline(desiredState);
|
||
|
}
|
||
|
|
||
|
|
||
|
StatusCode EvsStateControl::invokeGet(VehiclePropValue *pRequestedPropValue) {
|
||
|
StatusCode status = StatusCode::TRY_AGAIN;
|
||
|
|
||
|
// Call the Vehicle HAL, which will block until the callback is complete
|
||
|
mVehicle->get(*pRequestedPropValue,
|
||
|
[pRequestedPropValue, &status]
|
||
|
(StatusCode s, const VehiclePropValue& v) {
|
||
|
status = s;
|
||
|
if (s == StatusCode::OK) {
|
||
|
*pRequestedPropValue = v;
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool EvsStateControl::configureEvsPipeline(State desiredState) {
|
||
|
static bool isGlReady = false;
|
||
|
|
||
|
if (mCurrentState == desiredState) {
|
||
|
// Nothing to do here...
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Used by CarStats to accurately compute stats, it needs to be close to the beginning.
|
||
|
auto desiredStateTimeMillis = android::uptimeMillis();
|
||
|
|
||
|
LOG(DEBUG) << "Switching to state " << desiredState;
|
||
|
LOG(DEBUG) << " Current state " << mCurrentState
|
||
|
<< " has " << mCameraList[mCurrentState].size() << " cameras";
|
||
|
LOG(DEBUG) << " Desired state " << desiredState
|
||
|
<< " has " << mCameraList[desiredState].size() << " cameras";
|
||
|
|
||
|
if (!isGlReady && !isSfReady()) {
|
||
|
// Graphics is not ready yet; using CPU renderer.
|
||
|
if (mCameraList[desiredState].size() >= 1) {
|
||
|
mDesiredRenderer = std::make_unique<RenderPixelCopy>(mEvs,
|
||
|
mCameraList[desiredState][0]);
|
||
|
if (!mDesiredRenderer) {
|
||
|
LOG(ERROR) << "Failed to construct Pixel Copy renderer. Skipping state change.";
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
LOG(DEBUG) << "Unsupported, desiredState " << desiredState
|
||
|
<< " has " << mCameraList[desiredState].size() << " cameras.";
|
||
|
}
|
||
|
} else {
|
||
|
// Assumes that SurfaceFlinger is available always after being launched.
|
||
|
|
||
|
// Do we need a new direct view renderer?
|
||
|
if (mCameraList[desiredState].size() == 1) {
|
||
|
// We have a camera assigned to this state for direct view.
|
||
|
mDesiredRenderer = std::make_unique<RenderDirectView>(mEvs,
|
||
|
mCameraDescList[desiredState][0],
|
||
|
mConfig);
|
||
|
if (!mDesiredRenderer) {
|
||
|
LOG(ERROR) << "Failed to construct direct renderer. Skipping state change.";
|
||
|
return false;
|
||
|
}
|
||
|
} else if (mCameraList[desiredState].size() > 1 || desiredState == PARKING) {
|
||
|
//TODO(b/140668179): RenderTopView needs to be updated to use new
|
||
|
// ConfigManager.
|
||
|
mDesiredRenderer = std::make_unique<RenderTopView>(mEvs,
|
||
|
mCameraList[desiredState],
|
||
|
mConfig);
|
||
|
if (!mDesiredRenderer) {
|
||
|
LOG(ERROR) << "Failed to construct top view renderer. Skipping state change.";
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
LOG(DEBUG) << "Unsupported, desiredState " << desiredState
|
||
|
<< " has " << mCameraList[desiredState].size() << " cameras.";
|
||
|
}
|
||
|
|
||
|
// GL renderer is now ready.
|
||
|
isGlReady = true;
|
||
|
}
|
||
|
|
||
|
// Since we're changing states, shut down the current renderer
|
||
|
if (mCurrentRenderer != nullptr) {
|
||
|
mCurrentRenderer->deactivate();
|
||
|
mCurrentRenderer = nullptr; // It's a smart pointer, so destructs on assignment to null
|
||
|
}
|
||
|
|
||
|
// Now set the display state based on whether we have a video feed to show
|
||
|
if (mDesiredRenderer == nullptr) {
|
||
|
LOG(DEBUG) << "Turning off the display";
|
||
|
mDisplay->setDisplayState(EvsDisplayState::NOT_VISIBLE);
|
||
|
} else {
|
||
|
mCurrentRenderer = std::move(mDesiredRenderer);
|
||
|
|
||
|
// Start the camera stream
|
||
|
LOG(DEBUG) << "EvsStartCameraStreamTiming start time: "
|
||
|
<< android::elapsedRealtime() << " ms.";
|
||
|
if (!mCurrentRenderer->activate()) {
|
||
|
LOG(ERROR) << "New renderer failed to activate";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Activate the display
|
||
|
LOG(DEBUG) << "EvsActivateDisplayTiming start time: "
|
||
|
<< android::elapsedRealtime() << " ms.";
|
||
|
Return<EvsResult> result = mDisplay->setDisplayState(EvsDisplayState::VISIBLE_ON_NEXT_FRAME);
|
||
|
if (result != EvsResult::OK) {
|
||
|
LOG(ERROR) << "setDisplayState returned an error "
|
||
|
<< result.description();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Record our current state
|
||
|
LOG(INFO) << "Activated state " << desiredState;
|
||
|
mCurrentState = desiredState;
|
||
|
|
||
|
mFirstFrameIsDisplayed = false; // Got a new renderer, mark first frame is not displayed.
|
||
|
|
||
|
if (mCurrentRenderer != nullptr && desiredState == State::REVERSE) {
|
||
|
// Start computing the latency when the evs state changes.
|
||
|
mEvsStats.startComputingFirstFrameLatency(desiredStateTimeMillis);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|