/* * 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 "EvsCamera.h" #include #include #include #include #include #include #include #include namespace aidl::android::hardware::automotive::evs::implementation { // Arbitrary limit on number of graphics buffers allowed to be allocated // Safeguards against unreasonable resource consumption and provides a testable limit constexpr std::size_t kMaxBuffersInFlight = 100; // Minimum number of buffers to run a video stream constexpr int kMinimumBuffersInFlight = 1; EvsCamera::~EvsCamera() { shutdown(); } ndk::ScopedAStatus EvsCamera::doneWithFrame(const std::vector& buffers) { std::lock_guard lck(mMutex); for (const auto& desc : buffers) { returnBuffer_unsafe(desc.bufferId); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus EvsCamera::importExternalBuffers(const std::vector& buffers, int32_t* _aidl_return) { if (buffers.empty()) { LOG(DEBUG) << __func__ << ": Ignoring a request to import external buffers with an empty list."; return ndk::ScopedAStatus::ok(); } static auto& mapper = ::android::GraphicBufferMapper::get(); std::lock_guard lck(mMutex); std::size_t numBuffersToAdd = std::min(buffers.size(), kMaxBuffersInFlight - mAvailableFrames); if (numBuffersToAdd == 0) { LOG(WARNING) << __func__ << ": The number of buffers has hit the upper limit (" << kMaxBuffersInFlight << "). Stop importing."; return ndk::ScopedAStatus::ok(); } else if (numBuffersToAdd < buffers.size()) { LOG(WARNING) << "Exceeds the limit on the number of buffers. Only " << numBuffersToAdd << " buffers will be imported. " << buffers.size() << " are asked."; } const size_t before = mAvailableFrames; for (std::size_t idx = 0; idx < numBuffersToAdd; ++idx) { auto& buffer = buffers[idx]; const AHardwareBuffer_Desc* pDesc = reinterpret_cast(&buffer.buffer.description); buffer_handle_t handleToImport = ::android::dupFromAidl(buffer.buffer.handle); buffer_handle_t handleToStore = nullptr; if (handleToImport == nullptr) { LOG(WARNING) << "Failed to duplicate a memory handle. Ignoring a buffer " << buffer.bufferId; continue; } ::android::status_t result = mapper.importBuffer(handleToImport, pDesc->width, pDesc->height, pDesc->layers, pDesc->format, pDesc->usage, pDesc->stride, &handleToStore); if (result != ::android::NO_ERROR || handleToStore == nullptr || !increaseAvailableFrames_unsafe(handleToStore)) { LOG(WARNING) << "Failed to import a buffer " << buffer.bufferId; } } *_aidl_return = mAvailableFrames - before; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus EvsCamera::setMaxFramesInFlight(int32_t bufferCount) { std::lock_guard lock(mMutex); if (bufferCount < 1) { LOG(ERROR) << "Ignoring setMaxFramesInFlight with less than one buffer requested."; return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); } if (!setAvailableFrames_unsafe(bufferCount)) { LOG(ERROR) << "Failed to adjust the maximum number of frames in flight."; return ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::BUFFER_NOT_AVAILABLE)); } return ndk::ScopedAStatus::ok(); } void EvsCamera::freeOneFrame(const buffer_handle_t handle) { static auto& alloc = ::android::GraphicBufferAllocator::get(); alloc.free(handle); } bool EvsCamera::preVideoStreamStart_locked(const std::shared_ptr& receiver, ndk::ScopedAStatus& status, std::unique_lock& /* lck */) { if (!receiver) { LOG(ERROR) << __func__ << ": Null receiver."; status = ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::INVALID_ARG)); return false; } // If we've been displaced by another owner of the camera, then we can't do anything else if (mStreamState == StreamState::DEAD) { LOG(ERROR) << __func__ << ": Ignoring when camera has been lost."; status = ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::OWNERSHIP_LOST)); return false; } if (mStreamState != StreamState::STOPPED) { LOG(ERROR) << __func__ << ": Ignoring when a stream is already running."; status = ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::STREAM_ALREADY_RUNNING)); return false; } // If the client never indicated otherwise, configure ourselves for a single streaming buffer if (mAvailableFrames < kMinimumBuffersInFlight && !setAvailableFrames_unsafe(kMinimumBuffersInFlight)) { LOG(ERROR) << __func__ << "Failed to because we could not get a graphics buffer."; status = ndk::ScopedAStatus::fromServiceSpecificError( static_cast(EvsResult::BUFFER_NOT_AVAILABLE)); return false; } mStreamState = StreamState::RUNNING; return true; } bool EvsCamera::postVideoStreamStart_locked( const std::shared_ptr& /* receiver */, ndk::ScopedAStatus& /* status */, std::unique_lock& /* lck */) { return true; } bool EvsCamera::preVideoStreamStop_locked(ndk::ScopedAStatus& status, std::unique_lock& /* lck */) { if (mStreamState != StreamState::RUNNING) { // Terminate the stop process because a stream is not running. status = ndk::ScopedAStatus::ok(); return false; } mStreamState = StreamState::STOPPING; return true; } bool EvsCamera::postVideoStreamStop_locked(ndk::ScopedAStatus& /* status */, std::unique_lock& /* lck */) { mStreamState = StreamState::STOPPED; return true; } ndk::ScopedAStatus EvsCamera::startVideoStream( const std::shared_ptr& receiver) { bool needShutdown = false; auto status = ndk::ScopedAStatus::ok(); { std::unique_lock lck(mMutex); if (!preVideoStreamStart_locked(receiver, status, lck)) { return status; } if ((!startVideoStreamImpl_locked(receiver, status, lck) || !postVideoStreamStart_locked(receiver, status, lck)) && !status.isOk()) { needShutdown = true; } } if (needShutdown) { shutdown(); } return status; } ndk::ScopedAStatus EvsCamera::stopVideoStream() { bool needShutdown = false; auto status = ndk::ScopedAStatus::ok(); { std::unique_lock lck(mMutex); if (mStreamState != StreamState::RUNNING) { // We're already in the middle of the procedure to stop current data // stream. return status; } if ((!preVideoStreamStop_locked(status, lck) || !stopVideoStreamImpl_locked(status, lck) || !postVideoStreamStop_locked(status, lck)) && !status.isOk()) { needShutdown = true; } } if (needShutdown) { shutdown(); } return status; } ndk::ScopedAStatus EvsCamera::pauseVideoStream() { return ndk::ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::NOT_SUPPORTED)); } ndk::ScopedAStatus EvsCamera::resumeVideoStream() { return ndk::ScopedAStatus::fromServiceSpecificError(static_cast(EvsResult::NOT_SUPPORTED)); } bool EvsCamera::setAvailableFrames_unsafe(const std::size_t 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; } if (bufferCount > mAvailableFrames) { bool success = true; const std::size_t numBufferBeforeAlloc = mAvailableFrames; for (int numBufferToAllocate = bufferCount - mAvailableFrames; success && numBufferToAllocate > 0; --numBufferToAllocate) { buffer_handle_t handle = nullptr; const auto result = allocateOneFrame(&handle); if (result != ::android::NO_ERROR || !handle) { LOG(ERROR) << __func__ << ": Failed to allocate a graphics buffer. Error " << result << ", handle: " << handle; success = false; break; } success &= increaseAvailableFrames_unsafe(handle); } if (!success) { // Rollback when failure. for (int numBufferToRelease = mAvailableFrames - numBufferBeforeAlloc; numBufferToRelease > 0; --numBufferToRelease) { decreaseAvailableFrames_unsafe(); } return false; } } else { for (int numBufferToRelease = mAvailableFrames - std::max(bufferCount, mFramesInUse); numBufferToRelease > 0; --numBufferToRelease) { decreaseAvailableFrames_unsafe(); } if (mAvailableFrames > bufferCount) { // 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, asked: " << bufferCount << ", actual: " << mAvailableFrames << " -- too many buffers currently in use?"; } } return true; } void EvsCamera::shutdown() { stopVideoStream(); std::lock_guard lck(mMutex); closeAllBuffers_unsafe(); mStreamState = StreamState::DEAD; } void EvsCamera::closeAllBuffers_unsafe() { if (mFramesInUse > 0) { LOG(WARNING) << __func__ << ": Closing while " << mFramesInUse << " frame(s) are still in use."; } for (auto& buffer : mBuffers) { freeOneFrame(buffer.handle); buffer.handle = nullptr; } mBuffers.clear(); mBufferPosToId.clear(); mBufferIdToPos.clear(); } std::pair EvsCamera::useBuffer_unsafe() { if (mFramesInUse >= mAvailableFrames) { DCHECK_EQ(mFramesInUse, mAvailableFrames); return {kInvalidBufferID, nullptr}; } const std::size_t pos = mFramesInUse++; auto& buffer = mBuffers[pos]; DCHECK(!buffer.inUse); DCHECK(buffer.handle); buffer.inUse = true; return {mBufferPosToId[pos], buffer.handle}; } void EvsCamera::returnBuffer_unsafe(const std::size_t id) { if (id >= mBuffers.size()) { LOG(ERROR) << __func__ << ": ID out-of-bound. id: " << id << " max: " << mBuffers.size() - 1; return; } const std::size_t pos = mBufferIdToPos[id]; if (!mBuffers[pos].inUse) { LOG(ERROR) << __func__ << ": Ignoring returning frame " << id << " which is already free."; return; } DCHECK_LT(pos, mFramesInUse); const std::size_t last_in_use_pos = --mFramesInUse; swapBufferFrames_unsafe(pos, last_in_use_pos); mBuffers[last_in_use_pos].inUse = false; } bool EvsCamera::increaseAvailableFrames_unsafe(const buffer_handle_t handle) { if (mAvailableFrames >= kMaxBuffersInFlight) { LOG(WARNING) << __func__ << ": The number of buffers has hit the upper limit (" << kMaxBuffersInFlight << "). Stop increasing."; return false; } const std::size_t pos = mAvailableFrames++; if (mAvailableFrames > mBuffers.size()) { const std::size_t oldBufferSize = mBuffers.size(); mBuffers.resize(mAvailableFrames); mBufferPosToId.resize(mAvailableFrames); mBufferIdToPos.resize(mAvailableFrames); // Build position/ID mapping. for (std::size_t idx = oldBufferSize; idx < mBuffers.size(); ++idx) { mBufferPosToId[idx] = idx; mBufferIdToPos[idx] = idx; } } auto& buffer = mBuffers[pos]; DCHECK(!buffer.inUse); DCHECK(!buffer.handle); buffer.handle = handle; return true; } bool EvsCamera::decreaseAvailableFrames_unsafe() { if (mFramesInUse >= mAvailableFrames) { DCHECK_EQ(mFramesInUse, mAvailableFrames); return false; } const std::size_t pos = --mAvailableFrames; auto& buffer = mBuffers[pos]; DCHECK(!buffer.inUse); DCHECK(buffer.handle); freeOneFrame(buffer.handle); buffer.handle = nullptr; return true; } void EvsCamera::swapBufferFrames_unsafe(const std::size_t pos1, const std::size_t pos2) { if (pos1 == pos2) { return; } if (pos1 >= mBuffers.size() || pos2 >= mBuffers.size()) { LOG(ERROR) << __func__ << ": Index out-of-bound. pos1: " << pos1 << ", pos2: " << pos2 << ", buffer size: " << mBuffers.size(); return; } const std::size_t id1 = mBufferPosToId[pos1]; const std::size_t id2 = mBufferPosToId[pos2]; std::swap(mBufferPosToId[pos1], mBufferPosToId[pos2]); std::swap(mBufferIdToPos[id1], mBufferIdToPos[id2]); std::swap(mBuffers[pos1], mBuffers[pos2]); } } // namespace aidl::android::hardware::automotive::evs::implementation