/* * Copyright (C) 2019 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 #include #include #include "DisplayUseCase.h" #include "RenderDirectView.h" #include "Utils.h" namespace android { namespace automotive { namespace evs { namespace support { using android::hardware::configureRpcThreadpool; using android::hardware::joinRpcThreadpool; // TODO(b/130246434): since we don't support multi-display use case, there // should only be one DisplayUseCase. Add the logic to prevent more than // one DisplayUseCases running at the same time. DisplayUseCase::DisplayUseCase(string cameraId, BaseRenderCallback* callback) : BaseUseCase(vector(1, cameraId)) { mRenderCallback = callback; } DisplayUseCase::~DisplayUseCase() { if (mCurrentRenderer != nullptr) { mCurrentRenderer->deactivate(); mCurrentRenderer = nullptr; // It's a smart pointer, so destructs on assignment to null } mIsReadyToRun = false; if (mWorkerThread.joinable()) { mWorkerThread.join(); } } bool DisplayUseCase::initialize() { // Load our configuration information ConfigManager config; if (!config.initialize("/system/etc/automotive/evs_support_lib/camera_config.json")) { ALOGE("Missing or improper configuration for the EVS application. Exiting."); return false; } // 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 */); mResourceManager = ResourceManager::getInstance(); if (mResourceManager == nullptr) { ALOGE("Failed to get resource manager instance. Initialization failed."); return false; } // Request exclusive access to the EVS display ALOGI("Acquiring EVS Display"); mDisplay = mResourceManager->openDisplay(); if (mDisplay.get() == nullptr) { ALOGE("EVS Display unavailable. Exiting."); return false; } ALOGD("Requesting camera list"); for (auto&& info : config.getCameras()) { // This use case is currently a single camera use case. // Only one element is available in the camera id list. string cameraId = mCameraIds[0]; if (cameraId == info.cameraId) { mStreamHandler = mResourceManager->obtainStreamHandler(cameraId); if (mStreamHandler.get() == nullptr) { ALOGE("Failed to get a valid StreamHandler for %s", cameraId.c_str()); return false; } mIsInitialized = true; return true; } } ALOGE("Cannot find a match camera. Exiting"); return false; } // TODO(b/130246434): if user accidentally call this function twice, there is // no logic to handle that and it will causes issues. For example, the // mWorkerThread will be assigned twice and cause unexpected behavior. // We need to fix this issue. bool DisplayUseCase::startVideoStream() { // Initialize the use case. if (!mIsInitialized && !initialize()) { ALOGE("There is an error while initializing the use case. Exiting"); return false; } ALOGD("Attach use case to StreamHandler"); if (mRenderCallback != nullptr) { mStreamHandler->attachRenderCallback(mRenderCallback); } ALOGD("Start video streaming using worker thread"); mIsReadyToRun = true; mWorkerThread = std::thread([this]() { // We have a camera assigned to this state for direct view mCurrentRenderer = std::make_unique(); if (!mCurrentRenderer) { ALOGE("Failed to construct direct renderer. Exiting."); mIsReadyToRun = false; return; } // Now set the display state based on whether we have a video feed to show // Start the camera stream ALOGD("EvsStartCameraStreamTiming start time: %" PRId64 "ms", android::elapsedRealtime()); if (!mCurrentRenderer->activate()) { ALOGE("New renderer failed to activate. Exiting"); mIsReadyToRun = false; return; } // Activate the display ALOGD("EvsActivateDisplayTiming start time: %" PRId64 "ms", android::elapsedRealtime()); Return result = mDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME); if (result != EvsResult::OK) { ALOGE("setDisplayState returned an error (%d). Exiting.", (EvsResult)result); mIsReadyToRun = false; return; } if (!mStreamHandler->startStream()) { ALOGE("failed to start stream handler"); mIsReadyToRun = false; return; } while (mIsReadyToRun && streamFrame()); ALOGD("Worker thread stops."); }); return true; } void DisplayUseCase::stopVideoStream() { ALOGD("Stop video streaming in worker thread."); mIsReadyToRun = false; if (mStreamHandler == nullptr) { ALOGE("Failed to detach render callback since stream handler is null"); // Something may go wrong. Instead of to return this method right away, // we want to finish the remaining logic of this method to try to // release other resources. } else { mStreamHandler->detachRenderCallback(); } if (mResourceManager == nullptr) { ALOGE("Failed to release resources since resource manager is null"); } else { mResourceManager->releaseStreamHandler(mCameraIds[0]); mStreamHandler = nullptr; mResourceManager->closeDisplay(mDisplay); mDisplay = nullptr; // TODO(b/130246434): with the current logic, the initialize method will // be triggered every time when a pair of // stopVideoStream/startVideoStream is called. We might want to move // some heavy work away from initialize method so increase the // performance. // Sets mIsInitialzed to false so the initialize method will be // triggered when startVideoStream is called again. mIsInitialized = false; } return; } bool DisplayUseCase::streamFrame() { // Get the output buffer we'll use to display the imagery BufferDesc tgtBuffer = {}; mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc& buff) { tgtBuffer = buff; }); // TODO(b/130246434): if there is no new display frame available, shall we // still get display buffer? Shall we just skip and keep the display // un-refreshed? // We should explore this option. // If there is no display buffer available, skip it. if (tgtBuffer.memHandle == nullptr) { ALOGW("Didn't get requested output buffer -- skipping this frame."); // Return true since it won't affect next call. return true; } else { // If there is no new display frame available, re-use the old (held) // frame for display. // Otherwise, return the old (held) frame, fetch the newly available // frame from stream handler, and use the new frame for display // purposes. if (!mStreamHandler->newDisplayFrameAvailable()) { ALOGD("No new display frame is available. Re-use the old frame."); } else { ALOGD("Get new display frame, refreshing"); // If we already hold a camera image for display purposes, it's // time to return it to evs camera driver. if (mImageBuffer.memHandle.getNativeHandle() != nullptr) { mStreamHandler->doneWithFrame(mImageBuffer); } // Get the new image we want to use as our display content mImageBuffer = mStreamHandler->getNewDisplayFrame(); } // Render the image buffer to the display buffer bool result = mCurrentRenderer->drawFrame(tgtBuffer, mImageBuffer); // Send the finished display buffer back to display driver // Even if the rendering fails, we still want to return the display // buffer. mDisplay->returnTargetBufferForDisplay(tgtBuffer); return result; } } DisplayUseCase DisplayUseCase::createDefaultUseCase(string cameraId, BaseRenderCallback* callback) { return DisplayUseCase(cameraId, callback); } } // namespace support } // namespace evs } // namespace automotive } // namespace android