You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
552 lines
19 KiB
552 lines
19 KiB
/*
|
|
* Copyright 2020 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 "EvsUltrasonicsArray.h"
|
|
|
|
#include <android-base/logging.h>
|
|
#include <hidlmemory/mapping.h>
|
|
#include <log/log.h>
|
|
#include <time.h>
|
|
#include <utils/SystemClock.h>
|
|
#include <utils/Timers.h>
|
|
|
|
namespace android {
|
|
namespace hardware {
|
|
namespace automotive {
|
|
namespace evs {
|
|
namespace V1_1 {
|
|
namespace implementation {
|
|
|
|
// Arbitrary limit on number of data frames allowed to be allocated
|
|
// Safeguards against unreasonable resource consumption and provides a testable limit
|
|
const unsigned int kMaximumDataFramesInFlight = 100;
|
|
|
|
const uint32_t kMaxReadingsPerSensor = 5;
|
|
const uint32_t kMaxReceiversCount = 3;
|
|
|
|
const unsigned int kSharedMemoryMaxSize =
|
|
kMaxReadingsPerSensor * kMaxReceiversCount * 2 * sizeof(float);
|
|
|
|
// Target frame rate in frames per second.
|
|
const int kTargetFrameRate = 10;
|
|
|
|
namespace {
|
|
|
|
void fillMockArrayDesc(UltrasonicsArrayDesc& arrayDesc) {
|
|
arrayDesc.maxReadingsPerSensorCount = kMaxReadingsPerSensor;
|
|
arrayDesc.maxReceiversCount = kMaxReceiversCount;
|
|
|
|
const int kSensorCount = 3;
|
|
const float kMaxRange = 4000; // 4 metres.
|
|
const float kAngleOfMeasurement = 0.261799; // 15 degrees.
|
|
|
|
std::vector<UltrasonicSensor> sensors(kSensorCount);
|
|
|
|
// Sensor pointing forward on left side of front bumper.
|
|
sensors[0].maxRange = kMaxRange;
|
|
sensors[0].angleOfMeasurement = kAngleOfMeasurement;
|
|
sensors[0].pose = {{1, 0, 0, 0}, {-1000, 2000, 200}};
|
|
|
|
// Sensor pointing forward on center of front bumper.
|
|
sensors[1].maxRange = kMaxRange;
|
|
sensors[1].angleOfMeasurement = kAngleOfMeasurement;
|
|
sensors[1].pose = {{1, 0, 0, 0}, {0, 2000, 200}};
|
|
|
|
// Sensor pointing forward on right side of front bumper.
|
|
sensors[2].maxRange = kMaxRange;
|
|
sensors[2].angleOfMeasurement = kAngleOfMeasurement;
|
|
sensors[2].pose = {{1, 0, 0, 0}, {1000, 2000, 200}};
|
|
|
|
arrayDesc.sensors = sensors;
|
|
}
|
|
|
|
// Struct used by SerializeWaveformData().
|
|
struct WaveformData {
|
|
uint8_t receiverId;
|
|
std::vector<std::pair<float, float>> readings;
|
|
};
|
|
|
|
// Serializes data provided in waveformDataList to a shared memory data pointer.
|
|
// TODO(b/149950362): Add a common library for serialiazing and deserializing waveform data.
|
|
void SerializeWaveformData(const std::vector<WaveformData>& waveformDataList, uint8_t* pData) {
|
|
for (auto& waveformData : waveformDataList) {
|
|
// Set Id
|
|
memcpy(pData, &waveformData.receiverId, sizeof(uint8_t));
|
|
pData += sizeof(uint8_t);
|
|
|
|
for (auto& reading : waveformData.readings) {
|
|
// Set the time of flight.
|
|
memcpy(pData, &reading.first, sizeof(float));
|
|
pData += sizeof(float);
|
|
|
|
// Set the resonance.
|
|
memcpy(pData, &reading.second, sizeof(float));
|
|
pData += sizeof(float);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fills dataFrameDesc with mock data.
|
|
bool fillMockDataFrame(UltrasonicsDataFrameDesc& dataFrameDesc, sp<IMemory> pIMemory) {
|
|
dataFrameDesc.timestampNs = elapsedRealtimeNano();
|
|
|
|
const std::vector<uint8_t> transmittersIdList = {0};
|
|
dataFrameDesc.transmittersIdList = transmittersIdList;
|
|
|
|
const std::vector<uint8_t> recvIdList = {0, 1, 2};
|
|
dataFrameDesc.receiversIdList = recvIdList;
|
|
|
|
const std::vector<uint32_t> receiversReadingsCountList = {2, 2, 4};
|
|
dataFrameDesc.receiversReadingsCountList = receiversReadingsCountList;
|
|
|
|
const std::vector<WaveformData> waveformDataList = {
|
|
{recvIdList[0], { {1000, 0.1f}, {2000, 0.8f} }},
|
|
{recvIdList[1], { {1000, 0.1f}, {2000, 1.0f} }},
|
|
{recvIdList[2], { {1000, 0.1f}, {2000, 0.2f}, {4000, 0.2f}, {5000, 0.1f} }}
|
|
};
|
|
|
|
if (pIMemory.get() == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t* pData = (uint8_t*)((void*)pIMemory->getPointer());
|
|
|
|
pIMemory->update();
|
|
SerializeWaveformData(waveformDataList, pData);
|
|
pIMemory->commit();
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
EvsUltrasonicsArray::EvsUltrasonicsArray(const char* deviceName)
|
|
: mFramesAllowed(0), mFramesInUse(0), mStreamState(STOPPED) {
|
|
LOG(DEBUG) << "EvsUltrasonicsArray instantiated";
|
|
|
|
// Set up mock data for description.
|
|
mArrayDesc.ultrasonicsArrayId = deviceName;
|
|
fillMockArrayDesc(mArrayDesc);
|
|
|
|
// Assign allocator.
|
|
mShmemAllocator = IAllocator::getService("ashmem");
|
|
if (mShmemAllocator.get() == nullptr) {
|
|
LOG(ERROR) << "SurroundViewHidlTest getService ashmem failed";
|
|
}
|
|
}
|
|
|
|
sp<EvsUltrasonicsArray> EvsUltrasonicsArray::Create(const char* deviceName) {
|
|
return sp<EvsUltrasonicsArray>(new EvsUltrasonicsArray(deviceName));
|
|
}
|
|
|
|
EvsUltrasonicsArray::~EvsUltrasonicsArray() {
|
|
LOG(DEBUG) << "EvsUltrasonicsArray being destroyed";
|
|
forceShutdown();
|
|
}
|
|
|
|
// This gets called if another caller "steals" ownership of the ultrasonic array.
|
|
void EvsUltrasonicsArray::forceShutdown() {
|
|
LOG(DEBUG) << "EvsUltrasonicsArray forceShutdown";
|
|
|
|
// Make sure our output stream is cleaned up
|
|
// (It really should be already)
|
|
stopStream();
|
|
|
|
// Claim the lock while we work on internal state
|
|
std::lock_guard<std::mutex> lock(mAccessLock);
|
|
|
|
// Drop all the data frames we've been using
|
|
for (auto&& dataFrame : mDataFrames) {
|
|
if (dataFrame.inUse) {
|
|
LOG(ERROR) << "Error - releasing data frame despite remote ownership";
|
|
}
|
|
dataFrame.sharedMemory.clear();
|
|
}
|
|
mDataFrames.clear();
|
|
|
|
// Put this object into an unrecoverable error state since somebody else
|
|
// is going to own the underlying ultrasonic array now
|
|
mStreamState = DEAD;
|
|
}
|
|
|
|
UltrasonicsArrayDesc EvsUltrasonicsArray::GetMockArrayDesc(const char* deviceName) {
|
|
UltrasonicsArrayDesc ultrasonicsArrayDesc;
|
|
ultrasonicsArrayDesc.ultrasonicsArrayId = deviceName;
|
|
fillMockArrayDesc(ultrasonicsArrayDesc);
|
|
return ultrasonicsArrayDesc;
|
|
}
|
|
|
|
Return<void> EvsUltrasonicsArray::getUltrasonicArrayInfo(getUltrasonicArrayInfo_cb _get_info_cb) {
|
|
LOG(DEBUG) << "EvsUltrasonicsArray getUltrasonicsArrayInfo";
|
|
|
|
// Return the description for the get info callback.
|
|
_get_info_cb(mArrayDesc);
|
|
|
|
return Void();
|
|
}
|
|
|
|
Return<EvsResult> EvsUltrasonicsArray::setMaxFramesInFlight(uint32_t bufferCount) {
|
|
LOG(DEBUG) << "EvsUltrasonicsArray setMaxFramesInFlight";
|
|
|
|
// Lock mutex for performing changes to available frames.
|
|
std::lock_guard<std::mutex> lock(mAccessLock);
|
|
|
|
// We cannot function without at least one buffer to send data.
|
|
if (bufferCount < 1) {
|
|
LOG(ERROR) << "Ignoring setMaxFramesInFlight with less than one buffer requested";
|
|
return EvsResult::INVALID_ARG;
|
|
}
|
|
|
|
// Update our internal state of buffer count.
|
|
if (setAvailableFrames_Locked(bufferCount)) {
|
|
return EvsResult::OK;
|
|
} else {
|
|
return EvsResult::BUFFER_NOT_AVAILABLE;
|
|
}
|
|
|
|
return EvsResult::OK;
|
|
}
|
|
|
|
Return<void> EvsUltrasonicsArray::doneWithDataFrame(const UltrasonicsDataFrameDesc& dataFrameDesc) {
|
|
LOG(DEBUG) << "EvsUltrasonicsArray doneWithFrame";
|
|
|
|
std::lock_guard<std::mutex> lock(mAccessLock);
|
|
|
|
if (dataFrameDesc.dataFrameId >= mDataFrames.size()) {
|
|
LOG(ERROR) << "ignoring doneWithFrame called with invalid dataFrameId "
|
|
<< dataFrameDesc.dataFrameId << "(max is " << mDataFrames.size() - 1 << ")";
|
|
return Void();
|
|
}
|
|
|
|
if (!mDataFrames[dataFrameDesc.dataFrameId].inUse) {
|
|
LOG(ERROR) << "ignoring doneWithFrame called on frame " << dataFrameDesc.dataFrameId
|
|
<< "which is already free";
|
|
return Void();
|
|
}
|
|
|
|
// Mark the frame as available
|
|
mDataFrames[dataFrameDesc.dataFrameId].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 (dataFrameDesc.dataFrameId >= mFramesAllowed) {
|
|
// Find an empty slot lower in the array (which should always exist in this case)
|
|
for (auto&& dataFrame : mDataFrames) {
|
|
if (!dataFrame.sharedMemory.IsValid()) {
|
|
dataFrame.sharedMemory = mDataFrames[dataFrameDesc.dataFrameId].sharedMemory;
|
|
mDataFrames[dataFrameDesc.dataFrameId].sharedMemory.clear();
|
|
return Void();
|
|
}
|
|
}
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
Return<EvsResult> EvsUltrasonicsArray::startStream(
|
|
const ::android::sp<IEvsUltrasonicsArrayStream>& stream) {
|
|
LOG(DEBUG) << "EvsUltrasonicsArray startStream";
|
|
|
|
std::lock_guard<std::mutex> lock(mAccessLock);
|
|
|
|
if (mStreamState != STOPPED) {
|
|
LOG(ERROR) << "ignoring startStream 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 < 1) {
|
|
if (!setAvailableFrames_Locked(1)) {
|
|
LOG(ERROR)
|
|
<< "Failed to start stream because we couldn't get shared memory data buffer";
|
|
return EvsResult::BUFFER_NOT_AVAILABLE;
|
|
}
|
|
}
|
|
|
|
// Record the user's callback for use when we have a frame ready
|
|
mStream = stream;
|
|
|
|
// Start the frame generation thread
|
|
mStreamState = RUNNING;
|
|
mCaptureThread = std::thread([this]() { generateDataFrames(); });
|
|
|
|
return EvsResult::OK;
|
|
}
|
|
|
|
Return<void> EvsUltrasonicsArray::stopStream() {
|
|
LOG(DEBUG) << "EvsUltrasonicsArray stopStream";
|
|
|
|
bool streamStateStopping = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mAccessLock);
|
|
if (mStreamState == RUNNING) {
|
|
// Tell the GenerateFrames loop we want it to stop
|
|
mStreamState = STOPPING;
|
|
streamStateStopping = true;
|
|
}
|
|
}
|
|
|
|
if (streamStateStopping) {
|
|
// 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...";
|
|
mCaptureThread.join();
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mAccessLock);
|
|
mStreamState = STOPPED;
|
|
mStream = nullptr;
|
|
LOG(DEBUG) << "Stream marked STOPPED.";
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
bool EvsUltrasonicsArray::setAvailableFrames_Locked(unsigned bufferCount) {
|
|
if (bufferCount < 1) {
|
|
LOG(ERROR) << "Ignoring request to set buffer count to zero";
|
|
return false;
|
|
}
|
|
if (bufferCount > kMaximumDataFramesInFlight) {
|
|
LOG(ERROR) << "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;
|
|
LOG(INFO) << "Number of data frame buffers to add: " << 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
|
|
LOG(ERROR) << "Rolling back to previous frame queue size";
|
|
decreaseAvailableFrames_Locked(added);
|
|
return false;
|
|
}
|
|
} else if (mFramesAllowed > bufferCount) {
|
|
// A decrease is required
|
|
unsigned framesToRelease = mFramesAllowed - bufferCount;
|
|
LOG(INFO) << "Number of data frame buffers to reduce: " << 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.
|
|
LOG(ERROR) << "Buffer queue shrink failed -- too many buffers currently in use?";
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
EvsUltrasonicsArray::SharedMemory EvsUltrasonicsArray::allocateAndMapSharedMemory() {
|
|
SharedMemory sharedMemory;
|
|
|
|
// Check shared memory allocator is valid.
|
|
if (mShmemAllocator.get() == nullptr) {
|
|
LOG(ERROR) << "Shared memory allocator not initialized.";
|
|
return SharedMemory();
|
|
}
|
|
|
|
// Allocate memory.
|
|
bool allocateSuccess = false;
|
|
Return<void> result = mShmemAllocator->allocate(kSharedMemoryMaxSize,
|
|
[&](bool success, const hidl_memory& hidlMem) {
|
|
if (!success) {
|
|
return;
|
|
}
|
|
allocateSuccess = success;
|
|
sharedMemory.hidlMemory = hidlMem;
|
|
});
|
|
|
|
// Check result of allocated memory.
|
|
if (!result.isOk() || !allocateSuccess) {
|
|
LOG(ERROR) << "Shared memory allocation failed.";
|
|
return SharedMemory();
|
|
}
|
|
|
|
// Map shared memory.
|
|
sharedMemory.pIMemory = mapMemory(sharedMemory.hidlMemory);
|
|
if (sharedMemory.pIMemory.get() == nullptr) {
|
|
LOG(ERROR) << "Shared memory mapping failed.";
|
|
return SharedMemory();
|
|
}
|
|
|
|
// Return success.
|
|
return sharedMemory;
|
|
}
|
|
|
|
unsigned EvsUltrasonicsArray::increaseAvailableFrames_Locked(unsigned numToAdd) {
|
|
unsigned added = 0;
|
|
|
|
while (added < numToAdd) {
|
|
SharedMemory sharedMemory = allocateAndMapSharedMemory();
|
|
|
|
// If allocate and map fails, break.
|
|
if (!sharedMemory.IsValid()) {
|
|
break;
|
|
}
|
|
|
|
// Find a place to store the new buffer
|
|
bool stored = false;
|
|
for (auto&& dataFrame : mDataFrames) {
|
|
if (!dataFrame.sharedMemory.IsValid()) {
|
|
// Use this existing entry
|
|
dataFrame.sharedMemory = sharedMemory;
|
|
dataFrame.inUse = false;
|
|
stored = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!stored) {
|
|
// Add a BufferRecord wrapping this handle to our set of available buffers
|
|
mDataFrames.emplace_back(sharedMemory);
|
|
}
|
|
|
|
mFramesAllowed++;
|
|
added++;
|
|
}
|
|
|
|
return added;
|
|
}
|
|
|
|
unsigned EvsUltrasonicsArray::decreaseAvailableFrames_Locked(unsigned numToRemove) {
|
|
unsigned removed = 0;
|
|
|
|
for (auto&& dataFrame : mDataFrames) {
|
|
// Is this record not in use, but holding a buffer that we can free?
|
|
if (!dataFrame.inUse && dataFrame.sharedMemory.IsValid()) {
|
|
// Release buffer and update the record so we can recognize it as "empty"
|
|
dataFrame.sharedMemory.clear();
|
|
|
|
mFramesAllowed--;
|
|
removed++;
|
|
|
|
if (removed == numToRemove) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
// This is the asynchronous data frame generation thread that runs in parallel with the
|
|
// main serving thread. There is one for each active ultrasonic array instance.
|
|
void EvsUltrasonicsArray::generateDataFrames() {
|
|
LOG(DEBUG) << "Data frame generation loop started";
|
|
|
|
unsigned idx = 0;
|
|
|
|
while (true) {
|
|
bool timeForFrame = false;
|
|
|
|
nsecs_t startTime = elapsedRealtimeNano();
|
|
|
|
// Lock scope for updating shared state
|
|
{
|
|
std::lock_guard<std::mutex> 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 < mDataFrames.size(); idx++) {
|
|
if (!mDataFrames[idx].inUse && mDataFrames[idx].sharedMemory.IsValid()) {
|
|
// Found an available record, so stop looking
|
|
break;
|
|
}
|
|
}
|
|
if (idx >= mDataFrames.size()) {
|
|
// This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed
|
|
LOG(ERROR) << "Failed to find an available buffer slot";
|
|
} else {
|
|
// We're going to make the frame busy
|
|
mDataFrames[idx].inUse = true;
|
|
mFramesInUse++;
|
|
timeForFrame = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (timeForFrame) {
|
|
// Assemble the buffer description we'll transmit below
|
|
UltrasonicsDataFrameDesc mockDataFrameDesc;
|
|
mockDataFrameDesc.dataFrameId = idx;
|
|
mockDataFrameDesc.waveformsData = mDataFrames[idx].sharedMemory.hidlMemory;
|
|
|
|
// Fill mock waveform data.
|
|
fillMockDataFrame(mockDataFrameDesc, mDataFrames[idx].sharedMemory.pIMemory);
|
|
|
|
// Issue the (asynchronous) callback to the client -- can't be holding the lock
|
|
auto result = mStream->deliverDataFrame(mockDataFrameDesc);
|
|
if (result.isOk()) {
|
|
LOG(DEBUG) << "Delivered data frame id: " << mockDataFrameDesc.dataFrameId;
|
|
} 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);
|
|
mDataFrames[idx].inUse = false;
|
|
mFramesInUse--;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Sleep to generate frames at kTargetFrameRate.
|
|
static const nsecs_t kTargetFrameTimeUs = 1000 * 1000 / kTargetFrameRate;
|
|
const nsecs_t now = elapsedRealtimeNano();
|
|
const nsecs_t workTimeUs = (now - startTime) / 1000;
|
|
const nsecs_t sleepDurationUs = kTargetFrameTimeUs - workTimeUs;
|
|
if (sleepDurationUs > 0) {
|
|
usleep(sleepDurationUs);
|
|
}
|
|
}
|
|
|
|
// If we've been asked to stop, send an event to signal the actual end of stream
|
|
EvsEventDesc event;
|
|
event.aType = EvsEventType::STREAM_STOPPED;
|
|
auto result = mStream->notify(event);
|
|
if (!result.isOk()) {
|
|
LOG(ERROR) << "Error delivering end of stream marker";
|
|
}
|
|
}
|
|
|
|
} // namespace implementation
|
|
} // namespace V1_1
|
|
} // namespace evs
|
|
} // namespace automotive
|
|
} // namespace hardware
|
|
} // namespace android
|