blob: a2d1fb539f2f94ef8313390d4d432abfee33342a [file] [log] [blame]
/*
* 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.
*/
#define LOG_TAG "SurroundViewService"
#include <android-base/logging.h>
#include <android/hardware_buffer.h>
#include <utils/SystemClock.h>
#include "SurroundView2dSession.h"
#include "CoreLibSetupHelper.h"
using namespace android_auto::surround_view;
namespace android {
namespace hardware {
namespace automotive {
namespace sv {
namespace V1_0 {
namespace implementation {
static const char kGrayColor = 128;
static const int kNumChannels = 3;
static const int kFrameDelayInMilliseconds = 30;
SurroundView2dSession::SurroundView2dSession() :
mStreamState(STOPPED) {
mEvsCameraIds = {"0", "1", "2", "3"};
}
// Methods from ::android::hardware::automotive::sv::V1_0::ISurroundViewSession
Return<SvResult> SurroundView2dSession::startStream(
const sp<ISurroundViewStream>& stream) {
LOG(DEBUG) << __FUNCTION__;
scoped_lock<mutex> lock(mAccessLock);
if (!mIsInitialized && !initialize()) {
LOG(ERROR) << "There is an error while initializing the use case. "
<< "Exiting";
return SvResult::INTERNAL_ERROR;
}
if (mStreamState != STOPPED) {
LOG(ERROR) << "Ignoring startVideoStream call"
<< "when a stream is already running.";
return SvResult::INTERNAL_ERROR;
}
if (stream == nullptr) {
LOG(ERROR) << "The input stream is invalid";
return SvResult::INTERNAL_ERROR;
}
mStream = stream;
LOG(DEBUG) << "Notify SvEvent::STREAM_STARTED";
mStream->notify(SvEvent::STREAM_STARTED);
// Start the frame generation thread
mStreamState = RUNNING;
mCaptureThread = thread([this](){
generateFrames();
});
return SvResult::OK;
}
Return<void> SurroundView2dSession::stopStream() {
LOG(DEBUG) << __FUNCTION__;
unique_lock<mutex> lock(mAccessLock);
if (mStreamState == RUNNING) {
// 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) << __FUNCTION__ << "Waiting for stream thread to end...";
lock.unlock();
mCaptureThread.join();
lock.lock();
mStreamState = STOPPED;
mStream = nullptr;
LOG(DEBUG) << "Stream marked STOPPED.";
}
return {};
}
Return<void> SurroundView2dSession::doneWithFrames(
const SvFramesDesc& svFramesDesc){
LOG(DEBUG) << __FUNCTION__;
scoped_lock <mutex> lock(mAccessLock);
framesRecord.inUse = false;
(void)svFramesDesc;
return {};
}
// Methods from ISurroundView2dSession follow.
Return<void> SurroundView2dSession::get2dMappingInfo(
get2dMappingInfo_cb _hidl_cb) {
LOG(DEBUG) << __FUNCTION__;
_hidl_cb(mInfo);
return {};
}
Return<SvResult> SurroundView2dSession::set2dConfig(
const Sv2dConfig& sv2dConfig) {
LOG(DEBUG) << __FUNCTION__;
scoped_lock <mutex> lock(mAccessLock);
if (sv2dConfig.width <=0 || sv2dConfig.width > 4096) {
LOG(WARNING) << "The width of 2d config is out of the range (0, 4096]"
<< "Ignored!";
return SvResult::INVALID_ARG;
}
mConfig.width = sv2dConfig.width;
mConfig.blending = sv2dConfig.blending;
mHeight = mConfig.width * mInfo.height / mInfo.width;
if (mStream != nullptr) {
LOG(DEBUG) << "Notify SvEvent::CONFIG_UPDATED";
mStream->notify(SvEvent::CONFIG_UPDATED);
}
return SvResult::OK;
}
Return<void> SurroundView2dSession::get2dConfig(get2dConfig_cb _hidl_cb) {
LOG(DEBUG) << __FUNCTION__;
_hidl_cb(mConfig);
return {};
}
Return<void> SurroundView2dSession::projectCameraPoints(
const hidl_vec<Point2dInt>& points2dCamera,
const hidl_string& cameraId,
projectCameraPoints_cb _hidl_cb) {
LOG(DEBUG) << __FUNCTION__;
scoped_lock <mutex> lock(mAccessLock);
bool cameraIdFound = false;
for (auto& evsCameraId : mEvsCameraIds) {
if (cameraId == evsCameraId) {
cameraIdFound = true;
LOG(INFO) << "Camera id found.";
break;
}
}
if (!cameraIdFound) {
LOG(ERROR) << "Camera id not found.";
_hidl_cb({});
return {};
}
hidl_vec<Point2dFloat> outPoints;
outPoints.resize(points2dCamera.size());
int width = mConfig.width;
int height = mHeight;
for (int i=0; i<points2dCamera.size(); i++) {
// Assuming all the points in the image frame can be projected into 2d
// Surround View space. Otherwise cannot.
if (points2dCamera[i].x < 0 || points2dCamera[i].x > width-1 ||
points2dCamera[i].y < 0 || points2dCamera[i].y > height-1) {
LOG(WARNING) << __FUNCTION__
<< ": gets invalid 2d camera points. Ignored";
outPoints[i].isValid = false;
outPoints[i].x = 10000;
outPoints[i].y = 10000;
} else {
outPoints[i].isValid = true;
outPoints[i].x = 0;
outPoints[i].y = 0;
}
}
_hidl_cb(outPoints);
return {};
}
void SurroundView2dSession::generateFrames() {
int sequenceId = 0;
while(true) {
{
scoped_lock<mutex> lock(mAccessLock);
if (mStreamState != RUNNING) {
// Break out of our main thread loop
LOG(INFO) << "StreamState does not equal to RUNNING. "
<< "Exiting the loop";
break;
}
if (mOutputWidth != mConfig.width || mOutputHeight != mHeight) {
LOG(DEBUG) << "Config changed. Re-allocate memory."
<< " Old width: "
<< mOutputWidth
<< " Old height: "
<< mOutputHeight
<< " New width: "
<< mConfig.width
<< " New height: "
<< mHeight;
delete[] static_cast<char*>(mOutputPointer.data_pointer);
mOutputWidth = mConfig.width;
mOutputHeight = mHeight;
mOutputPointer.height = mOutputHeight;
mOutputPointer.width = mOutputWidth;
mOutputPointer.format = Format::RGB;
mOutputPointer.data_pointer =
new char[mOutputHeight * mOutputWidth * kNumChannels];
if (!mOutputPointer.data_pointer) {
LOG(ERROR) << "Memory allocation failed. Exiting.";
break;
}
Size2dInteger size = Size2dInteger(mOutputWidth, mOutputHeight);
mSurroundView->Update2dOutputResolution(size);
mSvTexture = new GraphicBuffer(mOutputWidth,
mOutputHeight,
HAL_PIXEL_FORMAT_RGB_888,
1,
GRALLOC_USAGE_HW_TEXTURE,
"SvTexture");
if (mSvTexture->initCheck() == OK) {
LOG(INFO) << "Successfully allocated Graphic Buffer";
} else {
LOG(ERROR) << "Failed to allocate Graphic Buffer";
break;
}
}
}
if (mSurroundView->Get2dSurroundView(mInputPointers, &mOutputPointer)) {
LOG(INFO) << "Get2dSurroundView succeeded";
} else {
LOG(ERROR) << "Get2dSurroundView failed. "
<< "Using memset to initialize to gray";
memset(mOutputPointer.data_pointer, kGrayColor,
mOutputHeight * mOutputWidth * kNumChannels);
}
void* textureDataPtr = nullptr;
mSvTexture->lock(GRALLOC_USAGE_SW_WRITE_OFTEN
| GRALLOC_USAGE_SW_READ_NEVER,
&textureDataPtr);
if (!textureDataPtr) {
LOG(ERROR) << "Failed to gain write access to GraphicBuffer!";
break;
}
// Note: there is a chance that the stride of the texture is not the same
// as the width. For example, when the input frame is 1920 * 1080, the
// width is 1080, but the stride is 2048. So we'd better copy the data line
// by line, instead of single memcpy.
uint8_t* writePtr = static_cast<uint8_t*>(textureDataPtr);
uint8_t* readPtr = static_cast<uint8_t*>(mOutputPointer.data_pointer);
const int readStride = mOutputWidth * kNumChannels;
const int writeStride = mSvTexture->getStride() * kNumChannels;
if (readStride == writeStride) {
memcpy(writePtr, readPtr, readStride * mSvTexture->getHeight());
} else {
for (int i=0; i<mSvTexture->getHeight(); i++) {
memcpy(writePtr, readPtr, readStride);
writePtr = writePtr + writeStride;
readPtr = readPtr + readStride;
}
}
LOG(INFO) << "memcpy finished";
mSvTexture->unlock();
ANativeWindowBuffer* buffer = mSvTexture->getNativeBuffer();
LOG(DEBUG) << "ANativeWindowBuffer->handle: "
<< buffer->handle;
framesRecord.frames.svBuffers.resize(1);
SvBuffer& svBuffer = framesRecord.frames.svBuffers[0];
svBuffer.viewId = 0;
svBuffer.hardwareBuffer.nativeHandle = buffer->handle;
AHardwareBuffer_Desc* pDesc =
reinterpret_cast<AHardwareBuffer_Desc *>(
&svBuffer.hardwareBuffer.description);
pDesc->width = mOutputWidth;
pDesc->height = mOutputHeight;
pDesc->layers = 1;
pDesc->usage = GRALLOC_USAGE_HW_TEXTURE;
pDesc->stride = mSvTexture->getStride();
pDesc->format = HAL_PIXEL_FORMAT_RGB_888;
framesRecord.frames.timestampNs = elapsedRealtimeNano();
framesRecord.frames.sequenceId = sequenceId++;
{
scoped_lock<mutex> lock(mAccessLock);
if (framesRecord.inUse) {
LOG(DEBUG) << "Notify SvEvent::FRAME_DROPPED";
mStream->notify(SvEvent::FRAME_DROPPED);
} else {
framesRecord.inUse = true;
mStream->receiveFrames(framesRecord.frames);
}
}
// TODO(b/150412555): adding delays explicitly. This delay should be
// removed when EVS camera is used.
this_thread::sleep_for(chrono::milliseconds(
kFrameDelayInMilliseconds));
}
// If we've been asked to stop, send an event to signal the actual
// end of stream
LOG(DEBUG) << "Notify SvEvent::STREAM_STOPPED";
mStream->notify(SvEvent::STREAM_STOPPED);
}
bool SurroundView2dSession::initialize() {
lock_guard<mutex> lock(mAccessLock, adopt_lock);
// TODO(b/150412555): ask core-lib team to add API description for "create"
// method in the .h file.
// The create method will never return a null pointer based the API
// description.
mSurroundView = unique_ptr<SurroundView>(Create());
mSurroundView->SetStaticData(GetCameras(), Get2dParams(), Get3dParams(),
GetUndistortionScales(), GetBoundingBox());
// TODO(b/150412555): remove after EVS camera is used
mInputPointers = mSurroundView->ReadImages(
"/etc/automotive/sv/cam0.png",
"/etc/automotive/sv/cam1.png",
"/etc/automotive/sv/cam2.png",
"/etc/automotive/sv/cam3.png");
if (mInputPointers.size() == 4
&& mInputPointers[0].cpu_data_pointer != nullptr) {
LOG(INFO) << "ReadImages succeeded";
} else {
LOG(ERROR) << "Failed to read images";
return false;
}
mOutputWidth = Get2dParams().resolution.width;
mOutputHeight = Get2dParams().resolution.height;
mConfig.width = mOutputWidth;
mConfig.blending = SvQuality::HIGH;
mHeight = mOutputHeight;
mOutputPointer.height = mOutputHeight;
mOutputPointer.width = mOutputWidth;
mOutputPointer.format = mInputPointers[0].format;
mOutputPointer.data_pointer = new char[
mOutputHeight * mOutputWidth * kNumChannels];
if (!mOutputPointer.data_pointer) {
LOG(ERROR) << "Memory allocation failed. Exiting.";
return false;
}
mSvTexture = new GraphicBuffer(mOutputWidth,
mOutputHeight,
HAL_PIXEL_FORMAT_RGB_888,
1,
GRALLOC_USAGE_HW_TEXTURE,
"SvTexture");
//TODO(b/150412555): the 2d mapping info should be read from config file.
mInfo.width = 8;
mInfo.height = 6;
mInfo.center.isValid = true;
mInfo.center.x = 0;
mInfo.center.y = 0;
if (mSvTexture->initCheck() == OK) {
LOG(INFO) << "Successfully allocated Graphic Buffer";
} else {
LOG(ERROR) << "Failed to allocate Graphic Buffer";
return false;
}
if (mSurroundView->Start2dPipeline()) {
LOG(INFO) << "Start2dPipeline succeeded";
} else {
LOG(ERROR) << "Start2dPipeline failed";
return false;
}
mIsInitialized = true;
return true;
}
} // namespace implementation
} // namespace V1_0
} // namespace sv
} // namespace automotive
} // namespace hardware
} // namespace android