| /* |
| * 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 "EvsMockCamera.h" |
| #include "ConfigManager.h" |
| #include "EvsEnumerator.h" |
| |
| #include <aidlcommonsupport/NativeHandle.h> |
| #include <ui/GraphicBufferAllocator.h> |
| #include <ui/GraphicBufferMapper.h> |
| #include <utils/SystemClock.h> |
| |
| #include <memory> |
| |
| namespace { |
| |
| using ::aidl::android::hardware::graphics::common::BufferUsage; |
| using ::ndk::ScopedAStatus; |
| |
| // Arbitrary limit on number of graphics buffers allowed to be allocated |
| // Safeguards against unreasonable resource consumption and provides a testable limit |
| constexpr unsigned kMaxBuffersInFlight = 100; |
| |
| // Minimum number of buffers to run a video stream |
| constexpr int kMinimumBuffersInFlight = 1; |
| |
| // Colors for the colorbar test pattern in ABGR format |
| constexpr uint32_t kColors[] = { |
| 0xFFFFFFFF, // white |
| 0xFF00FFFF, // yellow |
| 0xFFFFFF00, // cyan |
| 0xFF00FF00, // green |
| 0xFFFF00FF, // fuchsia |
| 0xFF0000FF, // red |
| 0xFFFF0000, // blue |
| 0xFF000000, // black |
| }; |
| constexpr size_t kNumColors = sizeof(kColors) / sizeof(kColors[0]); |
| |
| } // namespace |
| |
| namespace aidl::android::hardware::automotive::evs::implementation { |
| |
| EvsMockCamera::EvsMockCamera([[maybe_unused]] Sigil sigil, const char* id, |
| std::unique_ptr<ConfigManager::CameraInfo>& camInfo) |
| : mFramesAllowed(0), mFramesInUse(0), mStreamState(STOPPED), mCameraInfo(camInfo) { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| /* set a camera id */ |
| mDescription.id = id; |
| |
| /* set camera metadata */ |
| if (camInfo) { |
| uint8_t* ptr = reinterpret_cast<uint8_t*>(camInfo->characteristics); |
| const size_t len = get_camera_metadata_size(camInfo->characteristics); |
| mDescription.metadata.insert(mDescription.metadata.end(), ptr, ptr + len); |
| } |
| |
| // Initialize parameters. |
| initializeParameters(); |
| } |
| |
| EvsMockCamera::~EvsMockCamera() { |
| LOG(DEBUG) << __FUNCTION__; |
| shutdown(); |
| } |
| |
| void EvsMockCamera::initializeParameters() { |
| mParams.emplace( |
| CameraParam::BRIGHTNESS, |
| new CameraParameterDesc(/* min= */ 0, /* max= */ 255, /* step= */ 1, /* value= */ 255)); |
| mParams.emplace( |
| CameraParam::CONTRAST, |
| new CameraParameterDesc(/* min= */ 0, /* max= */ 255, /* step= */ 1, /* value= */ 255)); |
| mParams.emplace( |
| CameraParam::SHARPNESS, |
| new CameraParameterDesc(/* min= */ 0, /* max= */ 255, /* step= */ 1, /* value= */ 255)); |
| } |
| |
| // This gets called if another caller "steals" ownership of the camera |
| void EvsMockCamera::shutdown() { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| // Make sure our output stream is cleaned up |
| // (It really should be already) |
| stopVideoStream_impl(); |
| |
| // Claim the lock while we work on internal state |
| std::lock_guard lock(mAccessLock); |
| |
| // Drop all the graphics buffers we've been using |
| if (mBuffers.size() > 0) { |
| ::android::GraphicBufferAllocator& alloc(::android::GraphicBufferAllocator::get()); |
| for (auto&& rec : mBuffers) { |
| if (rec.inUse) { |
| LOG(WARNING) << "WARNING: releasing a buffer remotely owned."; |
| } |
| alloc.free(rec.handle); |
| rec.handle = nullptr; |
| } |
| mBuffers.clear(); |
| } |
| |
| // Put this object into an unrecoverable error state since somebody else |
| // is going to own the underlying camera now |
| mStreamState = DEAD; |
| } |
| |
| // Methods from ::aidl::android::hardware::automotive::evs::IEvsCamera follow. |
| ScopedAStatus EvsMockCamera::getCameraInfo(CameraDesc* _aidl_return) { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| // Send back our self description |
| *_aidl_return = mDescription; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::setMaxFramesInFlight(int32_t bufferCount) { |
| LOG(DEBUG) << __FUNCTION__ << ", bufferCount = " << bufferCount; |
| ; |
| |
| std::lock_guard lock(mAccessLock); |
| |
| // If we've been displaced by another owner of the camera, then we can't do anything else |
| if (mStreamState == DEAD) { |
| LOG(ERROR) << "Ignoring setMaxFramesInFlight call when camera has been lost."; |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::OWNERSHIP_LOST)); |
| } |
| |
| // We cannot function without at least one video buffer to send data |
| if (bufferCount < 1) { |
| LOG(ERROR) << "Ignoring setMaxFramesInFlight with less than one buffer requested."; |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::INVALID_ARG)); |
| } |
| |
| // Update our internal state |
| if (!setAvailableFrames_Locked(bufferCount)) { |
| LOG(ERROR) << "Failed to adjust the maximum number of frames in flight."; |
| return ScopedAStatus::fromServiceSpecificError( |
| static_cast<int>(EvsResult::BUFFER_NOT_AVAILABLE)); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::startVideoStream(const std::shared_ptr<IEvsCameraStream>& cb) { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| if (!cb) { |
| LOG(ERROR) << "A given stream callback is invalid."; |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::INVALID_ARG)); |
| } |
| |
| std::lock_guard lock(mAccessLock); |
| |
| // If we've been displaced by another owner of the camera, then we can't do anything else |
| if (mStreamState == DEAD) { |
| LOG(ERROR) << "Ignoring startVideoStream call when camera has been lost."; |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::OWNERSHIP_LOST)); |
| } |
| |
| if (mStreamState != STOPPED) { |
| LOG(ERROR) << "Ignoring startVideoStream call when a stream is already running."; |
| return ScopedAStatus::fromServiceSpecificError( |
| static_cast<int>(EvsResult::STREAM_ALREADY_RUNNING)); |
| } |
| |
| // If the client never indicated otherwise, configure ourselves for a single streaming buffer |
| if (mFramesAllowed < kMinimumBuffersInFlight && |
| !setAvailableFrames_Locked(kMinimumBuffersInFlight)) { |
| LOG(ERROR) << "Failed to start stream because we couldn't get a graphics buffer"; |
| return ScopedAStatus::fromServiceSpecificError( |
| static_cast<int>(EvsResult::BUFFER_NOT_AVAILABLE)); |
| } |
| |
| // Record the user's callback for use when we have a frame ready |
| mStream = cb; |
| |
| // Start the frame generation thread |
| mStreamState = RUNNING; |
| mCaptureThread = std::thread([this]() { generateFrames(); }); |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::doneWithFrame(const std::vector<BufferDesc>& list) { |
| std::lock_guard lock(mAccessLock); |
| for (const auto& desc : list) { |
| returnBufferLocked(desc.bufferId); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::stopVideoStream() { |
| LOG(DEBUG) << __FUNCTION__; |
| return stopVideoStream_impl(); |
| } |
| |
| ScopedAStatus EvsMockCamera::stopVideoStream_impl() { |
| std::unique_lock lock(mAccessLock); |
| |
| if (mStreamState != RUNNING) { |
| // Safely return here because a stream is not running. |
| return ScopedAStatus::ok(); |
| } |
| |
| // Tell the GenerateFrames loop we want it to stop |
| mStreamState = STOPPING; |
| |
| // Block outside the mutex until the "stop" flag has been acknowledged |
| // We won't send any more frames, but the client might still get some already in flight |
| LOG(DEBUG) << "Waiting for stream thread to end..."; |
| lock.unlock(); |
| if (mCaptureThread.joinable()) { |
| mCaptureThread.join(); |
| } |
| lock.lock(); |
| |
| mStreamState = STOPPED; |
| mStream = nullptr; |
| LOG(DEBUG) << "Stream marked STOPPED."; |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::getExtendedInfo(int32_t opaqueIdentifier, |
| std::vector<uint8_t>* opaqueValue) { |
| const auto it = mExtInfo.find(opaqueIdentifier); |
| if (it == mExtInfo.end()) { |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::INVALID_ARG)); |
| } else { |
| *opaqueValue = mExtInfo[opaqueIdentifier]; |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::setExtendedInfo(int32_t opaqueIdentifier, |
| const std::vector<uint8_t>& opaqueValue) { |
| mExtInfo.insert_or_assign(opaqueIdentifier, opaqueValue); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::getPhysicalCameraInfo([[maybe_unused]] const std::string& id, |
| CameraDesc* _aidl_return) { |
| LOG(DEBUG) << __FUNCTION__; |
| |
| // This method works exactly same as getCameraInfo() in EVS HW module. |
| *_aidl_return = mDescription; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::pauseVideoStream() { |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::NOT_SUPPORTED)); |
| } |
| |
| ScopedAStatus EvsMockCamera::resumeVideoStream() { |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::NOT_SUPPORTED)); |
| } |
| |
| ScopedAStatus EvsMockCamera::setPrimaryClient() { |
| /* Because EVS HW module reference implementation expects a single client at |
| * a time, this returns a success code always. |
| */ |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::forcePrimaryClient(const std::shared_ptr<IEvsDisplay>&) { |
| /* Because EVS HW module reference implementation expects a single client at |
| * a time, this returns a success code always. |
| */ |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::unsetPrimaryClient() { |
| /* Because EVS HW module reference implementation expects a single client at |
| * a time, there is no chance that this is called by the secondary client and |
| * therefore returns a success code always. |
| */ |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::getParameterList(std::vector<CameraParam>* _aidl_return) { |
| if (mCameraInfo) { |
| _aidl_return->resize(mCameraInfo->controls.size()); |
| auto idx = 0; |
| for (auto& [name, range] : mCameraInfo->controls) { |
| (*_aidl_return)[idx++] = name; |
| } |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::getIntParameterRange(CameraParam id, ParameterRange* _aidl_return) { |
| auto it = mParams.find(id); |
| if (it == mParams.end()) { |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::NOT_SUPPORTED)); |
| } |
| |
| _aidl_return->min = it->second->range.min; |
| _aidl_return->max = it->second->range.max; |
| _aidl_return->step = it->second->range.step; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::setIntParameter(CameraParam id, int32_t value, |
| std::vector<int32_t>* effectiveValue) { |
| auto it = mParams.find(id); |
| if (it == mParams.end()) { |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::NOT_SUPPORTED)); |
| } |
| |
| // Rounding down to the closest value. |
| int32_t candidate = value / it->second->range.step * it->second->range.step; |
| if (candidate < it->second->range.min || candidate > it->second->range.max) { |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::INVALID_ARG)); |
| } |
| |
| it->second->value = candidate; |
| effectiveValue->push_back(candidate); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::getIntParameter(CameraParam id, std::vector<int32_t>* value) { |
| auto it = mParams.find(id); |
| if (it == mParams.end()) { |
| return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::NOT_SUPPORTED)); |
| } |
| |
| value->push_back(it->second->value); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus EvsMockCamera::importExternalBuffers(const std::vector<BufferDesc>& buffers, |
| int32_t* _aidl_return) { |
| size_t numBuffersToAdd = buffers.size(); |
| if (numBuffersToAdd < 1) { |
| LOG(DEBUG) << "Ignoring a request to import external buffers with an empty list."; |
| return ScopedAStatus::ok(); |
| } |
| |
| std::lock_guard lock(mAccessLock); |
| if (numBuffersToAdd > (kMaxBuffersInFlight - mFramesAllowed)) { |
| numBuffersToAdd -= (kMaxBuffersInFlight - mFramesAllowed); |
| LOG(WARNING) << "Exceed the limit on the number of buffers. " << numBuffersToAdd |
| << " buffers will be imported only."; |
| } |
| |
| ::android::GraphicBufferMapper& mapper = ::android::GraphicBufferMapper::get(); |
| const size_t before = mFramesAllowed; |
| for (size_t i = 0; i < numBuffersToAdd; ++i) { |
| auto& b = buffers[i]; |
| const AHardwareBuffer_Desc* pDesc = |
| reinterpret_cast<const AHardwareBuffer_Desc*>(&b.buffer.description); |
| |
| buffer_handle_t handleToImport = ::android::dupFromAidl(b.buffer.handle); |
| buffer_handle_t handleToStore = nullptr; |
| if (handleToImport == nullptr) { |
| LOG(WARNING) << "Failed to duplicate a memory handle. Ignoring a buffer " << b.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) { |
| LOG(WARNING) << "Failed to import a buffer " << b.bufferId; |
| continue; |
| } |
| |
| bool stored = false; |
| for (auto&& rec : mBuffers) { |
| if (rec.handle != nullptr) { |
| continue; |
| } |
| |
| // Use this existing entry. |
| rec.handle = handleToStore; |
| rec.inUse = false; |
| stored = true; |
| break; |
| } |
| |
| if (!stored) { |
| // Add a BufferRecord wrapping this handle to our set of available buffers. |
| mBuffers.push_back(BufferRecord(handleToStore)); |
| } |
| ++mFramesAllowed; |
| } |
| |
| *_aidl_return = mFramesAllowed - before; |
| return ScopedAStatus::ok(); |
| } |
| |
| bool EvsMockCamera::setAvailableFrames_Locked(unsigned 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; |
| } |
| |
| // Is an increase required? |
| if (mFramesAllowed < bufferCount) { |
| // An increase is required |
| auto needed = bufferCount - mFramesAllowed; |
| LOG(INFO) << "Allocating " << needed << " buffers for camera frames"; |
| |
| auto added = increaseAvailableFrames_Locked(needed); |
| if (added != needed) { |
| // If we didn't add all the frames we needed, then roll back to the previous state |
| LOG(ERROR) << "Rolling back to previous frame queue size"; |
| decreaseAvailableFrames_Locked(added); |
| return false; |
| } |
| } else if (mFramesAllowed > bufferCount) { |
| // A decrease is required |
| auto framesToRelease = mFramesAllowed - bufferCount; |
| LOG(INFO) << "Returning " << framesToRelease << " camera frame buffers"; |
| |
| auto released = decreaseAvailableFrames_Locked(framesToRelease); |
| if (released != framesToRelease) { |
| // 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 -- too many buffers currently in use?"; |
| } |
| } |
| |
| return true; |
| } |
| |
| unsigned EvsMockCamera::increaseAvailableFrames_Locked(unsigned numToAdd) { |
| // Acquire the graphics buffer allocator |
| ::android::GraphicBufferAllocator& alloc(::android::GraphicBufferAllocator::get()); |
| |
| unsigned added = 0; |
| while (added < numToAdd) { |
| unsigned pixelsPerLine = 0; |
| buffer_handle_t memHandle = nullptr; |
| auto result = alloc.allocate(mWidth, mHeight, mFormat, 1, mUsage, &memHandle, |
| &pixelsPerLine, 0, "EvsMockCamera"); |
| if (result != ::android::NO_ERROR) { |
| LOG(ERROR) << "Error " << result << " allocating " << mWidth << " x " << mHeight |
| << " graphics buffer"; |
| break; |
| } |
| if (memHandle == nullptr) { |
| LOG(ERROR) << "We didn't get a buffer handle back from the allocator"; |
| break; |
| } |
| if (mStride > 0) { |
| if (mStride != pixelsPerLine) { |
| LOG(ERROR) << "We did not expect to get buffers with different strides!"; |
| } |
| } else { |
| // Gralloc defines stride in terms of pixels per line |
| mStride = pixelsPerLine; |
| } |
| |
| // Find a place to store the new buffer |
| auto stored = false; |
| for (auto&& rec : mBuffers) { |
| if (rec.handle == nullptr) { |
| // Use this existing entry |
| rec.handle = memHandle; |
| rec.inUse = false; |
| stored = true; |
| break; |
| } |
| } |
| if (!stored) { |
| // Add a BufferRecord wrapping this handle to our set of available buffers |
| mBuffers.push_back(BufferRecord(memHandle)); |
| } |
| |
| ++mFramesAllowed; |
| ++added; |
| } |
| |
| return added; |
| } |
| |
| unsigned EvsMockCamera::decreaseAvailableFrames_Locked(unsigned numToRemove) { |
| // Acquire the graphics buffer allocator |
| ::android::GraphicBufferAllocator& alloc(::android::GraphicBufferAllocator::get()); |
| |
| unsigned removed = 0; |
| for (auto&& rec : mBuffers) { |
| // Is this record not in use, but holding a buffer that we can free? |
| if ((rec.inUse == false) && (rec.handle != nullptr)) { |
| // Release buffer and update the record so we can recognize it as "empty" |
| alloc.free(rec.handle); |
| rec.handle = nullptr; |
| |
| --mFramesAllowed; |
| ++removed; |
| |
| if (removed == numToRemove) { |
| break; |
| } |
| } |
| } |
| |
| return removed; |
| } |
| |
| // This is the asynchronous frame generation thread that runs in parallel with the |
| // main serving thread. There is one for each active camera instance. |
| void EvsMockCamera::generateFrames() { |
| LOG(DEBUG) << "Frame generation loop started."; |
| |
| unsigned idx = 0; |
| while (true) { |
| bool timeForFrame = false; |
| const nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); |
| |
| // Lock scope for updating shared state |
| { |
| std::lock_guard lock(mAccessLock); |
| |
| if (mStreamState != RUNNING) { |
| // Break out of our main thread loop |
| break; |
| } |
| |
| // Are we allowed to issue another buffer? |
| if (mFramesInUse >= mFramesAllowed) { |
| // Can't do anything right now -- skip this frame |
| LOG(WARNING) << "Skipped a frame because too many are in flight."; |
| } else { |
| // Identify an available buffer to fill |
| for (idx = 0; idx < mBuffers.size(); idx++) { |
| if (!mBuffers[idx].inUse) { |
| if (mBuffers[idx].handle != nullptr) { |
| // Found an available record, so stop looking |
| break; |
| } |
| } |
| } |
| if (idx >= mBuffers.size()) { |
| // This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed |
| ALOGE("Failed to find an available buffer slot\n"); |
| } else { |
| // We're going to make the frame busy |
| mBuffers[idx].inUse = true; |
| mFramesInUse++; |
| timeForFrame = true; |
| } |
| } |
| } |
| |
| if (timeForFrame) { |
| using AidlPixelFormat = ::aidl::android::hardware::graphics::common::PixelFormat; |
| |
| // Assemble the buffer description we'll transmit below |
| buffer_handle_t memHandle = mBuffers[idx].handle; |
| BufferDesc newBuffer = { |
| .buffer = |
| { |
| .description = |
| { |
| .width = static_cast<int32_t>(mWidth), |
| .height = static_cast<int32_t>(mHeight), |
| .layers = 1, |
| .format = static_cast<AidlPixelFormat>(mFormat), |
| .usage = static_cast<BufferUsage>(mUsage), |
| .stride = static_cast<int32_t>(mStride), |
| }, |
| .handle = ::android::dupToAidl(memHandle), |
| }, |
| .bufferId = static_cast<int32_t>(idx), |
| .deviceId = mDescription.id, |
| .timestamp = static_cast<int64_t>(::android::elapsedRealtimeNano() * |
| 1e+3), // timestamps is in microseconds |
| }; |
| |
| // Write test data into the image buffer |
| fillMockFrame(memHandle, reinterpret_cast<const AHardwareBuffer_Desc*>( |
| &newBuffer.buffer.description)); |
| |
| // Issue the (asynchronous) callback to the client -- can't be holding the lock |
| auto flag = false; |
| if (mStream) { |
| std::vector<BufferDesc> frames; |
| frames.push_back(std::move(newBuffer)); |
| flag = mStream->deliverFrame(frames).isOk(); |
| } |
| |
| if (flag) { |
| LOG(DEBUG) << "Delivered " << memHandle << ", id = " << mBuffers[idx].handle; |
| } else { |
| // This can happen if the client dies and is likely unrecoverable. |
| // To avoid consuming resources generating failing calls, we stop sending |
| // frames. Note, however, that the stream remains in the "STREAMING" state |
| // until cleaned up on the main thread. |
| LOG(ERROR) << "Frame delivery call failed in the transport layer."; |
| |
| // Since we didn't actually deliver it, mark the frame as available |
| std::lock_guard<std::mutex> lock(mAccessLock); |
| mBuffers[idx].inUse = false; |
| mFramesInUse--; |
| } |
| } |
| |
| // We arbitrarily choose to generate frames at 15 fps to ensure we pass the 10fps test |
| // requirement |
| static const int kTargetFrameRate = 15; |
| static const nsecs_t kTargetFrameIntervalUs = 1000 * 1000 / kTargetFrameRate; |
| const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); |
| const nsecs_t elapsedTimeUs = (now - startTime) / 1000; |
| const nsecs_t sleepDurationUs = kTargetFrameIntervalUs - elapsedTimeUs; |
| if (sleepDurationUs > 0) { |
| usleep(sleepDurationUs); |
| } |
| } |
| |
| // If we've been asked to stop, send an event to signal the actual end of stream |
| EvsEventDesc event = { |
| .aType = EvsEventType::STREAM_STOPPED, |
| }; |
| if (!mStream->notify(event).isOk()) { |
| ALOGE("Error delivering end of stream marker"); |
| } |
| |
| return; |
| } |
| |
| void EvsMockCamera::fillMockFrame(buffer_handle_t handle, const AHardwareBuffer_Desc* pDesc) { |
| // Lock our output buffer for writing |
| uint32_t* pixels = nullptr; |
| ::android::GraphicBufferMapper& mapper = ::android::GraphicBufferMapper::get(); |
| mapper.lock(handle, GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER, |
| ::android::Rect(pDesc->width, pDesc->height), (void**)&pixels); |
| |
| // If we failed to lock the pixel buffer, we're about to crash, but log it first |
| if (!pixels) { |
| ALOGE("Camera failed to gain access to image buffer for writing"); |
| return; |
| } |
| |
| // Fill in the test pixels; the colorbar in ABGR format |
| for (unsigned row = 0; row < pDesc->height; row++) { |
| for (unsigned col = 0; col < pDesc->width; col++) { |
| const uint32_t index = col * kNumColors / pDesc->width; |
| pixels[col] = kColors[index]; |
| } |
| // Point to the next row |
| // NOTE: stride retrieved from gralloc is in units of pixels |
| pixels = pixels + pDesc->stride; |
| } |
| |
| // Release our output buffer |
| mapper.unlock(handle); |
| } |
| |
| void EvsMockCamera::returnBufferLocked(const uint32_t bufferId) { |
| if (bufferId >= mBuffers.size()) { |
| ALOGE("ignoring doneWithFrame called with invalid bufferId %d (max is %zu)", bufferId, |
| mBuffers.size() - 1); |
| return; |
| } |
| |
| if (!mBuffers[bufferId].inUse) { |
| ALOGE("ignoring doneWithFrame called on frame %d which is already free", bufferId); |
| return; |
| } |
| |
| // Mark the frame as available |
| mBuffers[bufferId].inUse = false; |
| mFramesInUse--; |
| |
| // If this frame's index is high in the array, try to move it down |
| // to improve locality after mFramesAllowed has been reduced. |
| if (bufferId >= mFramesAllowed) { |
| // Find an empty slot lower in the array (which should always exist in this case) |
| for (auto&& rec : mBuffers) { |
| if (rec.handle == nullptr) { |
| rec.handle = mBuffers[bufferId].handle; |
| mBuffers[bufferId].handle = nullptr; |
| break; |
| } |
| } |
| } |
| } |
| |
| std::shared_ptr<EvsMockCamera> EvsMockCamera::Create(const char* deviceName) { |
| std::unique_ptr<ConfigManager::CameraInfo> nullCamInfo = nullptr; |
| |
| return Create(deviceName, nullCamInfo); |
| } |
| |
| std::shared_ptr<EvsMockCamera> EvsMockCamera::Create( |
| const char* deviceName, std::unique_ptr<ConfigManager::CameraInfo>& camInfo, |
| [[maybe_unused]] const Stream* streamCfg) { |
| std::shared_ptr<EvsMockCamera> c = |
| ndk::SharedRefBase::make<EvsMockCamera>(Sigil{}, deviceName, camInfo); |
| if (!c) { |
| LOG(ERROR) << "Failed to instantiate EvsMockCamera."; |
| return nullptr; |
| } |
| |
| // Use the first resolution from the list for the testing |
| // TODO(b/214835237): Uses a given Stream configuration to choose the best |
| // stream configuration. |
| auto it = camInfo->streamConfigurations.begin(); |
| c->mWidth = it->second.width; |
| c->mHeight = it->second.height; |
| c->mDescription.vendorFlags = 0xFFFFFFFF; // Arbitrary test value |
| |
| c->mFormat = HAL_PIXEL_FORMAT_RGBA_8888; |
| c->mUsage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE | |
| GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY; |
| |
| return c; |
| } |
| |
| } // namespace aidl::android::hardware::automotive::evs::implementation |