/* * 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 "EvsCamera.h" #include "ConfigManager.h" #include "EvsEnumerator.h" #include #include #include namespace { // 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 uint32_t kNumColors = sizeof(kColors) / sizeof(kColors[0]); } // namespace namespace android::hardware::automotive::evs::V1_1::implementation { using V1_0::EvsResult; EvsCamera::EvsCamera(const char* id, std::unique_ptr& camInfo) : mFramesAllowed(0), mFramesInUse(0), mStreamState(STOPPED), mCameraInfo(camInfo) { ALOGD("%s", __FUNCTION__); /* set a camera id */ mDescription.v1.cameraId = id; /* set camera metadata */ mDescription.metadata.setToExternal((uint8_t*)camInfo->characteristics, get_camera_metadata_size(camInfo->characteristics)); } EvsCamera::~EvsCamera() { ALOGD("%s", __FUNCTION__); forceShutdown(); } // This gets called if another caller "steals" ownership of the camera void EvsCamera::forceShutdown() { ALOGD("%s", __FUNCTION__); // Make sure our output stream is cleaned up // (It really should be already) stopVideoStream(); // 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) { GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); for (auto&& rec : mBuffers) { if (rec.inUse) { ALOGE("Error - releasing buffer despite remote ownership"); } 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 ::android::hardware::automotive::evs::V1_0::IEvsCamera follow. Return EvsCamera::getCameraInfo(getCameraInfo_cb _hidl_cb) { ALOGD("%s", __FUNCTION__); // Send back our self description _hidl_cb(mDescription.v1); return {}; } Return EvsCamera::setMaxFramesInFlight(uint32_t bufferCount) { ALOGD("%s, bufferCount = %u", __FUNCTION__, 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) { ALOGE("ignoring setMaxFramesInFlight call when camera has been lost."); return EvsResult::OWNERSHIP_LOST; } // We cannot function without at least one video buffer to send data if (bufferCount < 1) { ALOGE("Ignoring setMaxFramesInFlight with less than one buffer requested"); return EvsResult::INVALID_ARG; } // Update our internal state if (setAvailableFrames_Locked(bufferCount)) { return EvsResult::OK; } else { return EvsResult::BUFFER_NOT_AVAILABLE; } } Return EvsCamera::startVideoStream(const ::android::sp& stream) { ALOGD("%s", __FUNCTION__); 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) { ALOGE("ignoring startVideoStream call when camera has been lost."); return EvsResult::OWNERSHIP_LOST; } if (mStreamState != STOPPED) { ALOGE("ignoring startVideoStream call when a stream is already running."); return EvsResult::STREAM_ALREADY_RUNNING; } // If the client never indicated otherwise, configure ourselves for a single streaming buffer if (mFramesAllowed < kMinimumBuffersInFlight) { if (!setAvailableFrames_Locked(kMinimumBuffersInFlight)) { ALOGE("Failed to start stream because we couldn't get a graphics buffer"); return EvsResult::BUFFER_NOT_AVAILABLE; } } // Record the user's callback for use when we have a frame ready mStream = IEvsCameraStream::castFrom(stream).withDefault(nullptr); if (!mStream) { ALOGE("Default implementation does not support v1.0 IEvsCameraStream"); return EvsResult::INVALID_ARG; } // Start the frame generation thread mStreamState = RUNNING; mCaptureThread = std::thread([this]() { generateFrames(); }); return EvsResult::OK; } Return EvsCamera::doneWithFrame(const V1_0::BufferDesc& buffer) { std::lock_guard lock(mAccessLock); returnBufferLocked(buffer.bufferId, buffer.memHandle); return {}; } Return EvsCamera::stopVideoStream() { ALOGD("%s", __FUNCTION__); std::unique_lock lock(mAccessLock); if (mStreamState != RUNNING) { return {}; } // 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 ALOGD("Waiting for stream thread to end..."); lock.unlock(); if (mCaptureThread.joinable()) { mCaptureThread.join(); } lock.lock(); mStreamState = STOPPED; mStream = nullptr; ALOGD("Stream marked STOPPED."); return {}; } Return EvsCamera::getExtendedInfo(uint32_t opaqueIdentifier) { ALOGD("%s", __FUNCTION__); std::lock_guard lock(mAccessLock); const auto it = mExtInfo.find(opaqueIdentifier); if (it == mExtInfo.end()) { // Return zero by default as required by the spec return 0; } else { return it->second[0]; } } Return EvsCamera::setExtendedInfo([[maybe_unused]] uint32_t opaqueIdentifier, [[maybe_unused]] int32_t opaqueValue) { ALOGD("%s", __FUNCTION__); 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) { ALOGE("ignoring setExtendedInfo call when camera has been lost."); return EvsResult::OWNERSHIP_LOST; } mExtInfo.insert_or_assign(opaqueIdentifier, opaqueValue); return EvsResult::OK; } // Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow. Return EvsCamera::getCameraInfo_1_1(getCameraInfo_1_1_cb _hidl_cb) { ALOGD("%s", __FUNCTION__); // Send back our self description _hidl_cb(mDescription); return {}; } Return EvsCamera::getPhysicalCameraInfo([[maybe_unused]] const hidl_string& id, getCameraInfo_1_1_cb _hidl_cb) { ALOGD("%s", __FUNCTION__); // This works exactly same as getCameraInfo_1_1() in default implementation. _hidl_cb(mDescription); return {}; } Return EvsCamera::doneWithFrame_1_1(const hidl_vec& buffers) { ALOGD("%s", __FUNCTION__); std::lock_guard lock(mAccessLock); for (auto&& buffer : buffers) { returnBufferLocked(buffer.bufferId, buffer.buffer.nativeHandle); } return EvsResult::OK; } Return EvsCamera::pauseVideoStream() { ALOGD("%s", __FUNCTION__); // Default implementation does not support this. return EvsResult::UNDERLYING_SERVICE_ERROR; } Return EvsCamera::resumeVideoStream() { ALOGD("%s", __FUNCTION__); // Default implementation does not support this. return EvsResult::UNDERLYING_SERVICE_ERROR; } Return EvsCamera::setMaster() { ALOGD("%s", __FUNCTION__); // Default implementation does not expect multiple subscribers and therefore // return a success code always. return EvsResult::OK; } Return EvsCamera::forceMaster(const sp&) { ALOGD("%s", __FUNCTION__); // Default implementation does not expect multiple subscribers and therefore // return a success code always. return EvsResult::OK; } Return EvsCamera::unsetMaster() { ALOGD("%s", __FUNCTION__); // Default implementation does not expect multiple subscribers and therefore // return a success code always. return EvsResult::OK; } Return EvsCamera::getParameterList(getParameterList_cb _hidl_cb) { ALOGD("%s", __FUNCTION__); hidl_vec hidlCtrls; hidlCtrls.resize(mCameraInfo->controls.size()); unsigned idx = 0; for (auto& [cid, cfg] : mCameraInfo->controls) { hidlCtrls[idx++] = cid; } _hidl_cb(hidlCtrls); return {}; } Return EvsCamera::getIntParameterRange(CameraParam id, getIntParameterRange_cb _hidl_cb) { ALOGD("%s", __FUNCTION__); auto it = mCameraInfo->controls.find(id); if (it == mCameraInfo->controls.end()) { _hidl_cb(0, 0, 0); } else { _hidl_cb(std::get<0>(it->second), std::get<1>(it->second), std::get<2>(it->second)); } return {}; } Return EvsCamera::setIntParameter(CameraParam id, int32_t value, setIntParameter_cb _hidl_cb) { ALOGD("%s", __FUNCTION__); mParams.insert_or_assign(id, value); _hidl_cb(EvsResult::OK, {value}); return {}; } Return EvsCamera::getIntParameter([[maybe_unused]] CameraParam id, getIntParameter_cb _hidl_cb) { ALOGD("%s", __FUNCTION__); auto it = mParams.find(id); std::vector values; if (it == mParams.end()) { _hidl_cb(EvsResult::INVALID_ARG, values); } else { values.push_back(it->second); _hidl_cb(EvsResult::OK, values); } return {}; } Return EvsCamera::setExtendedInfo_1_1(uint32_t opaqueIdentifier, const hidl_vec& opaqueValue) { ALOGD("%s", __FUNCTION__); mExtInfo.insert_or_assign(opaqueIdentifier, opaqueValue); return EvsResult::OK; } Return EvsCamera::getExtendedInfo_1_1(uint32_t opaqueIdentifier, getExtendedInfo_1_1_cb _hidl_cb) { ALOGD("%s", __FUNCTION__); auto status = EvsResult::OK; hidl_vec value; const auto it = mExtInfo.find(opaqueIdentifier); if (it == mExtInfo.end()) { status = EvsResult::INVALID_ARG; } else { value = it->second; } _hidl_cb(status, value); return {}; } Return EvsCamera::importExternalBuffers([[maybe_unused]] const hidl_vec& buffers, importExternalBuffers_cb _hidl_cb) { auto numBuffersToAdd = buffers.size(); if (numBuffersToAdd < 1) { ALOGD("No buffers to add"); _hidl_cb(EvsResult::OK, mFramesAllowed); return {}; } { std::scoped_lock lock(mAccessLock); if (numBuffersToAdd > (kMaxBuffersInFlight - mFramesAllowed)) { numBuffersToAdd -= (kMaxBuffersInFlight - mFramesAllowed); ALOGW("Exceed the limit on number of buffers. %" PRIu64 " buffers will be added only.", static_cast(numBuffersToAdd)); } GraphicBufferMapper& mapper = GraphicBufferMapper::get(); const auto before = mFramesAllowed; for (auto i = 0; i < numBuffersToAdd; ++i) { // TODO: reject if external buffer is configured differently. auto& b = buffers[i]; const AHardwareBuffer_Desc* pDesc = reinterpret_cast(&b.buffer.description); // Import a buffer to add buffer_handle_t memHandle = nullptr; status_t result = mapper.importBuffer(b.buffer.nativeHandle, pDesc->width, pDesc->height, 1, pDesc->format, pDesc->usage, pDesc->stride, &memHandle); if (result != android::NO_ERROR || !memHandle) { ALOGW("Failed to import a buffer %d", b.bufferId); continue; } 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.emplace_back(memHandle); } ++mFramesAllowed; } _hidl_cb(EvsResult::OK, mFramesAllowed - before); return {}; } } bool EvsCamera::setAvailableFrames_Locked(unsigned bufferCount) { if (bufferCount < kMinimumBuffersInFlight) { ALOGE("Ignoring request to set buffer count below the minimum number of buffers to run a " "video stream"); return false; } if (bufferCount > kMaxBuffersInFlight) { ALOGE("Rejecting buffer request in excess of internal limit"); return false; } // Is an increase required? if (mFramesAllowed < bufferCount) { // An increase is required unsigned needed = bufferCount - mFramesAllowed; ALOGI("Allocating %d buffers for camera frames", needed); unsigned added = increaseAvailableFrames_Locked(needed); if (added != needed) { // If we didn't add all the frames we needed, then roll back to the previous state ALOGE("Rolling back to previous frame queue size"); decreaseAvailableFrames_Locked(added); return false; } } else if (mFramesAllowed > bufferCount) { // A decrease is required unsigned framesToRelease = mFramesAllowed - bufferCount; ALOGI("Returning %d camera frame buffers", framesToRelease); unsigned 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. ALOGE("Buffer queue shrink failed -- too many buffers currently in use?"); } } return true; } unsigned EvsCamera::increaseAvailableFrames_Locked(unsigned numToAdd) { // Acquire the graphics buffer allocator GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); unsigned added = 0; while (added < numToAdd) { buffer_handle_t memHandle = nullptr; status_t result = alloc.allocate(mWidth, mHeight, mFormat, 1, mUsage, &memHandle, &mStride, 0, "EvsCamera"); if (result != NO_ERROR) { ALOGE("Error %d allocating %d x %d graphics buffer", result, mWidth, mHeight); break; } if (!memHandle) { ALOGE("We didn't get a buffer handle back from the allocator"); break; } // Find a place to store the new buffer bool 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(std::move(BufferRecord(memHandle))); } mFramesAllowed++; added++; } return added; } unsigned EvsCamera::decreaseAvailableFrames_Locked(unsigned numToRemove) { // Acquire the graphics buffer allocator GraphicBufferAllocator& alloc(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 EvsCamera::generateFrames() { ALOGD("Frame generation loop started"); unsigned idx; while (true) { bool timeForFrame = false; 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 ALOGW("Skipped a frame because too many are in flight\n"); } 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) { // Assemble the buffer description we'll transmit below BufferDesc newBuffer = {}; AHardwareBuffer_Desc* pDesc = reinterpret_cast(&newBuffer.buffer.description); pDesc->width = mWidth; pDesc->height = mHeight; pDesc->layers = 1; pDesc->format = mFormat; pDesc->usage = mUsage; pDesc->stride = mStride; newBuffer.buffer.nativeHandle = mBuffers[idx].handle; newBuffer.pixelSize = sizeof(uint32_t); newBuffer.bufferId = idx; newBuffer.deviceId = mDescription.v1.cameraId; newBuffer.timestamp = elapsedRealtimeNano() * 1e+3; // timestamps is in microseconds // Write test data into the image buffer fillTestFrame(newBuffer); // Issue the (asynchronous) callback to the client -- can't be holding the lock auto result = mStream->deliverFrame_1_1({newBuffer}); if (result.isOk()) { ALOGD("Delivered %p as id %d", newBuffer.buffer.nativeHandle.getNativeHandle(), newBuffer.bufferId); } 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. ALOGE("Frame delivery call failed in the transport layer."); // Since we didn't actually deliver it, mark the frame as available std::lock_guard lock(mAccessLock); mBuffers[idx].inUse = false; mFramesInUse--; break; } } // 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 EvsCamera::fillTestFrame(const BufferDesc& buff) { // Lock our output buffer for writing uint32_t* pixels = nullptr; const AHardwareBuffer_Desc* pDesc = reinterpret_cast(&buff.buffer.description); GraphicBufferMapper& mapper = GraphicBufferMapper::get(); mapper.lock(buff.buffer.nativeHandle, 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(buff.buffer.nativeHandle); } void EvsCamera::fillTestFrame(const V1_0::BufferDesc& buff) { BufferDesc newBuffer = { .buffer.nativeHandle = buff.memHandle, .pixelSize = buff.pixelSize, .bufferId = buff.bufferId, }; AHardwareBuffer_Desc* pDesc = reinterpret_cast(&newBuffer.buffer.description); *pDesc = { buff.width, // width buff.height, // height 1, // layers, always 1 for EVS buff.format, // One of AHardwareBuffer_Format buff.usage, // Combination of AHardwareBuffer_UsageFlags buff.stride, // Row stride in pixels 0, // Reserved 0 // Reserved }; return fillTestFrame(newBuffer); } void EvsCamera::returnBufferLocked(const uint32_t bufferId, const buffer_handle_t memHandle) { if (memHandle == nullptr) { ALOGE("ignoring doneWithFrame called with null handle"); } else if (bufferId >= mBuffers.size()) { ALOGE("ignoring doneWithFrame called with invalid bufferId %d (max is %zu)", bufferId, mBuffers.size() - 1); } else if (!mBuffers[bufferId].inUse) { ALOGE("ignoring doneWithFrame called on frame %d which is already free", bufferId); } else { // 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; } } } } } sp EvsCamera::Create(const char* deviceName) { std::unique_ptr nullCamInfo = nullptr; return Create(deviceName, nullCamInfo); } sp EvsCamera::Create(const char* deviceName, std::unique_ptr& camInfo, [[maybe_unused]] const Stream* streamCfg) { sp evsCamera = new EvsCamera(deviceName, camInfo); if (evsCamera == nullptr) { 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(); evsCamera->mWidth = it->second[1]; evsCamera->mHeight = it->second[2]; evsCamera->mDescription.v1.vendorFlags = 0xFFFFFFFF; // Arbitrary test value evsCamera->mFormat = HAL_PIXEL_FORMAT_RGBA_8888; evsCamera->mUsage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE | GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY; return evsCamera; } } // namespace android::hardware::automotive::evs::V1_1::implementation