| /* |
| * 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 <aidl/android/hardware/automotive/evs/EvsResult.h> |
| #include <aidlcommonsupport/NativeHandle.h> |
| #include <android-base/logging.h> |
| #include <android/hardware_buffer.h> |
| #include <ui/GraphicBufferAllocator.h> |
| #include <ui/GraphicBufferMapper.h> |
| |
| #include <cstddef> |
| #include <mutex> |
| |
| 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<evs::BufferDesc>& 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<evs::BufferDesc>& 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<const AHardwareBuffer_Desc*>(&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<int>(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<int>(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<evs::IEvsCameraStream>& receiver, |
| ndk::ScopedAStatus& status, |
| std::unique_lock<std::mutex>& /* lck */) { |
| if (!receiver) { |
| LOG(ERROR) << __func__ << ": Null receiver."; |
| status = ndk::ScopedAStatus::fromServiceSpecificError( |
| static_cast<int>(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<int>(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<int>(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<int>(EvsResult::BUFFER_NOT_AVAILABLE)); |
| return false; |
| } |
| mStreamState = StreamState::RUNNING; |
| return true; |
| } |
| |
| bool EvsCamera::postVideoStreamStart_locked( |
| const std::shared_ptr<evs::IEvsCameraStream>& /* receiver */, |
| ndk::ScopedAStatus& /* status */, std::unique_lock<std::mutex>& /* lck */) { |
| return true; |
| } |
| |
| bool EvsCamera::preVideoStreamStop_locked(ndk::ScopedAStatus& status, |
| std::unique_lock<std::mutex>& /* 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<std::mutex>& /* lck */) { |
| mStreamState = StreamState::STOPPED; |
| return true; |
| } |
| |
| ndk::ScopedAStatus EvsCamera::startVideoStream( |
| const std::shared_ptr<evs::IEvsCameraStream>& 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 ((!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<int>(EvsResult::NOT_SUPPORTED)); |
| } |
| |
| ndk::ScopedAStatus EvsCamera::resumeVideoStream() { |
| return ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int>(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<std::size_t, buffer_handle_t> 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 |