EVS 应用
- 1、EVS启动
- 2、EvsStateControl.cpp 控制管理
- 2.1 EvsStateControl初始化
- 2.2 EvsVehicleListener.h唤起处理`EvsStateControl::updateLoop()`
- 3、EVS 应用逻辑流程
android12-release
增强型视觉系统 (EVS)
1、EVS启动
Android 包含与
EVS 管理器
和车载 HAL
通信的 EVS 应用的原生 C++ 参考实现,以提供基本的后视摄像头功能。该应用应在系统启动过程的早期启动,根据可用的相机和汽车状态(车轮和转向灯状态)显示合适的视频。原始设备制造商 (OEM) 可以使用自己的汽车专用逻辑和呈现来修改或替换 EVS 应用。
packages/services/Car/cpp/evs/apps/default/Android.bp
packages/services/Car/cpp/evs/apps/default/evs_app.rc
packages/services/Car/cpp/evs/apps/default/evs_app.cpp
evs_app.rc解析
主要入口evs_app.cpp#main()
:
ConfigManager config
加载我们的配置信息:
const char* CONFIG_DEFAULT_PATH = "/system/etc/automotive/evs/config.json";
const char* CONFIG_OVERRIDE_PATH = "/system/etc/automotive/evs/config_override.json";
pEvs = IEvsEnumerator::getService(evsServiceName);
获取EVS 管理器
服务,evsServiceName为default、EvsEnumeratorHw 或 EvsEnumeratorHw-Mock
pDisplay = pEvs->openDisplay_1_1(displayId);
获取用于专门与系统的 EVS 显示进行交互的接口对象pVnet = IVehicle::getService()
连接Vehicle HAL
;注册属性监听class EvsVehicleListener : public IVehicleCallback
:
subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION)
车辆档位属性监听
subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE)
车辆转向信号灯属性监听pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config)
转态控制器创建并启动pStateController->startUpdateLoop()
pEvsListener->run(pStateController);
等到车辆属性事件,waitForEvents(5000)
每隔一段时间唤醒并验证当前的状态“以防万一”
// Main entry point
int main(int argc, char** argv)
{
LOG(INFO) << "EVS app starting";
// Register a signal handler
registerSigHandler();
// Set up default behavior, then check for command line options
bool useVehicleHal = true;
bool printHelp = false;
const char* evsServiceName = "default";
int displayId = -1;
bool useExternalMemory = false;
android_pixel_format_t extMemoryFormat = HAL_PIXEL_FORMAT_RGBA_8888;
int32_t mockGearSignal = static_cast<int32_t>(VehicleGear::GEAR_REVERSE);
for (int i=1; i< argc; i++) {
if (strcmp(argv[i], "--test") == 0) {
useVehicleHal = false;
} else if (strcmp(argv[i], "--hw") == 0) {
evsServiceName = "EvsEnumeratorHw";
} else if (strcmp(argv[i], "--mock") == 0) {
evsServiceName = "EvsEnumeratorHw-Mock";
} else if (strcmp(argv[i], "--help") == 0) {
printHelp = true;
} else if (strcmp(argv[i], "--display") == 0) {
displayId = std::stoi(argv[++i]);
} else if (strcmp(argv[i], "--extmem") == 0) {
useExternalMemory = true;
if (i + 1 >= argc) {
// use RGBA8888 by default
LOG(INFO) << "External buffer format is not set. "
<< "RGBA8888 will be used.";
} else {
if (!convertStringToFormat(argv[i + 1], &extMemoryFormat)) {
LOG(WARNING) << "Color format string " << argv[i + 1]
<< " is unknown or not supported. RGBA8888 will be used.";
} else {
// move the index
++i;
}
}
} else if (strcmp(argv[i], "--gear") == 0) {
// Gear signal to simulate
i += 1; // increase an index to next argument
if (strcasecmp(argv[i], "Park") == 0) {
mockGearSignal = static_cast<int32_t>(VehicleGear::GEAR_PARK);
} else if (strcasecmp(argv[i], "Reverse") != 0) {
LOG(WARNING) << "Unknown gear signal, " << argv[i] << ", is ignored "
<< "and the reverse signal will be used instead";
}
} else {
printf("Ignoring unrecognized command line arg '%s'\n", argv[i]);
printHelp = true;
}
}
if (printHelp) {
printf("Options include:\n");
printf(" --test\n\tDo not talk to Vehicle Hal, "
"but simulate a given mock gear signal instead\n");
printf(" --gear\n\tMock gear signal for the test mode.");
printf(" Available options are Reverse and Park (case insensitive)\n");
printf(" --hw\n\tBypass EvsManager by connecting directly to EvsEnumeratorHw\n");
printf(" --mock\n\tConnect directly to EvsEnumeratorHw-Mock\n");
printf(" --display\n\tSpecify the display to use. If this is not set, the first"
"display in config.json's list will be used.\n");
printf(" --extmem <format>\n\t"
"Application allocates buffers to capture camera frames. "
"Available format strings are (case insensitive):\n");
printf("\t\tRGBA8888: 4x8-bit RGBA format. This is the default format to be used "
"when no format is specified.\n");
printf("\t\tYV12: YUV420 planar format with a full resolution Y plane "
"followed by a V values, with U values last.\n");
printf("\t\tNV21: A biplanar format with a full resolution Y plane "
"followed by a single chrome plane with weaved V and U values.\n");
printf("\t\tYUYV: Packed format with a half horizontal chrome resolution. "
"Known as YUV4:2:2.\n");
return EXIT_FAILURE;
}
// Load our configuration information
ConfigManager config;
if (!config.initialize(CONFIG_OVERRIDE_PATH)) {
if (!config.initialize(CONFIG_DEFAULT_PATH)) {
LOG(ERROR) << "Missing or improper configuration for the EVS application. Exiting.";
return EXIT_FAILURE;
}
}
// Set thread pool size to one to avoid concurrent events from the HAL.
// This pool will handle the EvsCameraStream callbacks.
// Note: This _will_ run in parallel with the EvsListener run() loop below which
// runs the application logic that reacts to the async events.
configureRpcThreadpool(1, false /* callerWillJoin */);
// Construct our async helper object
sp<EvsVehicleListener> pEvsListener = new EvsVehicleListener();
// Get the EVS manager service
LOG(INFO) << "Acquiring EVS Enumerator";
pEvs = IEvsEnumerator::getService(evsServiceName);
if (pEvs.get() == nullptr) {
LOG(ERROR) << "getService(" << evsServiceName
<< ") returned NULL. Exiting.";
return EXIT_FAILURE;
}
// Request exclusive access to the EVS display
LOG(INFO) << "Acquiring EVS Display";
// We'll use an available display device.
displayId = config.setActiveDisplayId(displayId);
if (displayId < 0) {
PLOG(ERROR) << "EVS Display is unknown. Exiting.";
return EXIT_FAILURE;
}
pDisplay = pEvs->openDisplay_1_1(displayId);
if (pDisplay.get() == nullptr) {
LOG(ERROR) << "EVS Display unavailable. Exiting.";
return EXIT_FAILURE;
}
config.useExternalMemory(useExternalMemory);
config.setExternalMemoryFormat(extMemoryFormat);
// Set a mock gear signal for the test mode
config.setMockGearSignal(mockGearSignal);
// Connect to the Vehicle HAL so we can monitor state
sp<IVehicle> pVnet;
if (useVehicleHal) {
LOG(INFO) << "Connecting to Vehicle HAL";
pVnet = IVehicle::getService();
if (pVnet.get() == nullptr) {
LOG(ERROR) << "Vehicle HAL getService returned NULL. Exiting.";
return EXIT_FAILURE;
} else {
// Register for vehicle state change callbacks we care about
// Changes in these values are what will trigger a reconfiguration of the EVS pipeline
if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION)) {
LOG(ERROR) << "Without gear notification, we can't support EVS. Exiting.";
return EXIT_FAILURE;
}
if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE)) {
LOG(WARNING) << "Didn't get turn signal notifications, so we'll ignore those.";
}
}
} else {
LOG(WARNING) << "Test mode selected, so not talking to Vehicle HAL";
}
// Configure ourselves for the current vehicle state at startup
LOG(INFO) << "Constructing state controller";
pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config);
if (!pStateController->startUpdateLoop()) {
LOG(ERROR) << "Initial configuration failed. Exiting.";
return EXIT_FAILURE;
}
// Run forever, reacting to events as necessary
LOG(INFO) << "Entering running state";
pEvsListener->run(pStateController);
// In normal operation, we expect to run forever, but in some error conditions we'll quit.
// One known example is if another process preempts our registration for our service name.
LOG(ERROR) << "EVS Listener stopped. Exiting.";
return EXIT_SUCCESS;
}
在
android12-release
上C++ EVS 示例应用
是位于packages/services/Car/cpp/evs/apps
2、EvsStateControl.cpp 控制管理
2.1 EvsStateControl初始化
pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config)
:
mVehicle(pVnet)
:VehicleHal 车辆属性服务;参考 【IVI】VehicleService启动、【IVI】车载设备硬件抽象层VHALmEvs(pEvs)
:EVS 管理器
服务mDisplay(pDisplay)
:与系统的 EVS 显示进行交互mConfig(config)
:EVS json配置mCurrentState(OFF)
:当前状态mEvsStats(EvsStats::build())
:evs状态mEvs->getCameraList_1_1()
:系统中所有相机的说明的矢量mCameraList、mCameraDescList
:保存相关相机说明信息CameraDesc_1_1
hardware/interfaces/automotive/evs/1.1/types.hal
hardware/interfaces/automotive/evs/1.0/types.hal
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";
}
2.2 EvsVehicleListener.h唤起处理EvsStateControl::updateLoop()
VehicleProperty::GEAR_SELECTION、VehicleProperty::TURN_SIGNAL_STATE
事件接收onPropertyEvent
,mEventCond.notify_one()
处理EvsVehicleListener#run
死循环调用pStateController->postCommand(cmd)
mWakeSignal.notify_all()
唤起最终调用EvsStateControl::updateLoop()
CHECK_VEHICLE_STATE
这里提示 Just running selectStateForCurrentConditions below will take care of this
selectStateForCurrentConditions()
就是根据车辆档位和转向信号灯属性
判断configureEvsPipeline
处理状态和renderermCurrentRenderer->drawFrame(convertBufferDesc(tgtBuffer))
生成我们的输出图像
packages/services/Car/cpp/evs/apps/default/EvsVehicleListener.h
void run(EvsStateControl *pStateController) {
while (true) {
// Wait until we have an event to which to react
// (wake up and validate our current state "just in case" every so often)
waitForEvents(5000);
// If we were delivered an event (or it's been a while) update as necessary
EvsStateControl::Command cmd = {
.operation = EvsStateControl::Op::CHECK_VEHICLE_STATE,
.arg1 = 0,
.arg2 = 0,
};
pStateController->postCommand(cmd);
}
}
packages/services/Car/cpp/evs/apps/default/EvsStateControl.cpp
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";
}