Add EVS v1.1 interface
Extends existing v1.0 interfaces to support more functionalities and
adds corresponding test cases.
Bug: 135472573
Test: VTS
Change-Id: I5245238e6a18ddc2c0451aaa21a9cce55c6207eb
Signed-off-by: Changyeon Jo <changyeon@google.com>
diff --git a/automotive/evs/1.1/Android.bp b/automotive/evs/1.1/Android.bp
new file mode 100644
index 0000000..d2e85f1
--- /dev/null
+++ b/automotive/evs/1.1/Android.bp
@@ -0,0 +1,22 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+ name: "android.hardware.automotive.evs@1.1",
+ root: "android.hardware",
+ vndk: {
+ enabled: true,
+ },
+ srcs: [
+ "types.hal",
+ "IEvsCamera.hal",
+ "IEvsCameraStream.hal",
+ ],
+ interfaces: [
+ "android.hardware.automotive.evs@1.0",
+ "android.hardware.graphics.common@1.0",
+ "android.hardware.graphics.common@1.1",
+ "android.hardware.graphics.common@1.2",
+ "android.hidl.base@1.0",
+ ],
+ gen_java: true,
+}
diff --git a/automotive/evs/1.1/IEvsCamera.hal b/automotive/evs/1.1/IEvsCamera.hal
new file mode 100644
index 0000000..e7f6bb7
--- /dev/null
+++ b/automotive/evs/1.1/IEvsCamera.hal
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+package android.hardware.automotive.evs@1.1;
+
+import @1.0::IEvsCamera;
+import @1.0::EvsResult;
+import IEvsCameraStream;
+
+/**
+ * Represents a single camera and is the primary interface for capturing images.
+ */
+interface IEvsCamera extends @1.0::IEvsCamera {
+ /**
+ * Requests to pause EVS camera stream events.
+ *
+ * Like stopVideoStream(), events may continue to arrive for some time
+ * after this call returns. Delivered frame buffers must be returned.
+ *
+ * @return result EvsResult::OK is returned if this call is successful.
+ */
+ pauseVideoStream() generates (EvsResult result);
+
+ /**
+ * Requests to resume EVS camera stream.
+ *
+ * @return result EvsResult::OK is returned if this call is successful.
+ */
+ resumeVideoStream() generates (EvsResult result);
+
+ /**
+ * Returns a frame that was delivered by to the IEvsCameraStream.
+ *
+ * When done consuming a frame delivered to the IEvsCameraStream
+ * interface, it must be returned to the IEvsCamera for reuse.
+ * A small, finite number of buffers are available (possibly as small
+ * as one), and if the supply is exhausted, no further frames may be
+ * delivered until a buffer is returned.
+ *
+ * @param buffer A buffer to be returned.
+ * @return result Return EvsResult::OK if this call is successful.
+ */
+ doneWithFrame_1_1(BufferDesc buffer) generates (EvsResult result);
+};
diff --git a/automotive/evs/1.1/IEvsCameraStream.hal b/automotive/evs/1.1/IEvsCameraStream.hal
new file mode 100644
index 0000000..cd058a5
--- /dev/null
+++ b/automotive/evs/1.1/IEvsCameraStream.hal
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package android.hardware.automotive.evs@1.1;
+
+import @1.0::IEvsCameraStream;
+
+/**
+ * Implemented on client side to receive asynchronous video frame deliveries.
+ */
+interface IEvsCameraStream extends @1.0::IEvsCameraStream {
+ /**
+ * Receives calls from the HAL each time an event happens.
+ *
+ * @param event EVS event with possible event information.
+ */
+ oneway notifyEvent(EvsEvent event);
+};
diff --git a/automotive/evs/1.1/default/Android.bp b/automotive/evs/1.1/default/Android.bp
new file mode 100644
index 0000000..411f0ff
--- /dev/null
+++ b/automotive/evs/1.1/default/Android.bp
@@ -0,0 +1,32 @@
+cc_binary {
+ name: "android.hardware.automotive.evs@1.1-service",
+ defaults: ["hidl_defaults"],
+ proprietary: true,
+ relative_install_path: "hw",
+ srcs: [
+ "service.cpp",
+ "EvsCamera.cpp",
+ "EvsEnumerator.cpp",
+ "EvsDisplay.cpp"
+ ],
+ init_rc: ["android.hardware.automotive.evs@1.1-service.rc"],
+
+ shared_libs: [
+ "android.hardware.automotive.evs@1.0",
+ "android.hardware.automotive.evs@1.1",
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "libhardware",
+ "libhidlbase",
+ "libhidltransport",
+ "liblog",
+ "libui",
+ "libutils",
+ ],
+
+ cflags: [
+ "-O0",
+ "-g",
+ ],
+}
diff --git a/automotive/evs/1.1/default/EvsCamera.cpp b/automotive/evs/1.1/default/EvsCamera.cpp
new file mode 100644
index 0000000..62d9826
--- /dev/null
+++ b/automotive/evs/1.1/default/EvsCamera.cpp
@@ -0,0 +1,584 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.automotive.evs@1.1-service"
+
+#include "EvsCamera.h"
+#include "EvsEnumerator.h"
+
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+// Special camera names for which we'll initialize alternate test data
+const char EvsCamera::kCameraName_Backup[] = "backup";
+
+
+// Arbitrary limit on number of graphics buffers allowed to be allocated
+// Safeguards against unreasonable resource consumption and provides a testable limit
+const unsigned MAX_BUFFERS_IN_FLIGHT = 100;
+
+
+EvsCamera::EvsCamera(const char *id) :
+ mFramesAllowed(0),
+ mFramesInUse(0),
+ mStreamState(STOPPED) {
+
+ ALOGD("EvsCamera instantiated");
+
+ mDescription.cameraId = id;
+
+ // Set up dummy data for testing
+ if (mDescription.cameraId == kCameraName_Backup) {
+ mWidth = 640; // full NTSC/VGA
+ mHeight = 480; // full NTSC/VGA
+ mDescription.vendorFlags = 0xFFFFFFFF; // Arbitrary value
+ } else {
+ mWidth = 320; // 1/2 NTSC/VGA
+ mHeight = 240; // 1/2 NTSC/VGA
+ }
+
+ mFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+ mUsage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE |
+ GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY;
+}
+
+
+EvsCamera::~EvsCamera() {
+ ALOGD("EvsCamera being destroyed");
+ forceShutdown();
+}
+
+
+//
+// This gets called if another caller "steals" ownership of the camera
+//
+void EvsCamera::forceShutdown()
+{
+ ALOGD("EvsCamera forceShutdown");
+
+ // 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 <std::mutex> 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<void> EvsCamera::getCameraInfo(getCameraInfo_cb _hidl_cb) {
+ ALOGD("getCameraInfo");
+
+ // Send back our self description
+ _hidl_cb(mDescription);
+ return Void();
+}
+
+
+Return<EvsResult> EvsCamera::setMaxFramesInFlight(uint32_t bufferCount) {
+ ALOGD("setMaxFramesInFlight");
+ std::lock_guard<std::mutex> 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<EvsResult> EvsCamera::startVideoStream(const ::android::sp<IEvsCameraStream_1_0>& stream) {
+ ALOGD("startVideoStream");
+ std::lock_guard<std::mutex> 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 < 1) {
+ if (!setAvailableFrames_Locked(1)) {
+ 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_1_1::castFrom(stream).withDefault(nullptr);
+ if (mStream == nullptr) {
+ 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<void> EvsCamera::doneWithFrame(const BufferDesc_1_0& buffer) {
+ std::lock_guard <std::mutex> lock(mAccessLock);
+ returnBuffer(buffer.bufferId, buffer.memHandle);
+
+ return Void();
+}
+
+
+Return<void> EvsCamera::stopVideoStream() {
+ ALOGD("stopVideoStream");
+ std::unique_lock <std::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
+ ALOGD("Waiting for stream thread to end..");
+ lock.unlock();
+ mCaptureThread.join();
+ lock.lock();
+
+ mStreamState = STOPPED;
+ mStream = nullptr;
+ ALOGD("Stream marked STOPPED.");
+ }
+
+ return Void();
+}
+
+
+Return<int32_t> EvsCamera::getExtendedInfo(uint32_t opaqueIdentifier) {
+ ALOGD("getExtendedInfo");
+ std::lock_guard<std::mutex> lock(mAccessLock);
+
+ // For any single digit value, return the index itself as a test value
+ if (opaqueIdentifier <= 9) {
+ return opaqueIdentifier;
+ }
+
+ // Return zero by default as required by the spec
+ return 0;
+}
+
+
+Return<EvsResult> EvsCamera::setExtendedInfo(uint32_t /*opaqueIdentifier*/, int32_t /*opaqueValue*/) {
+ ALOGD("setExtendedInfo");
+ std::lock_guard<std::mutex> 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;
+ }
+
+ // We don't store any device specific information in this implementation
+ return EvsResult::INVALID_ARG;
+}
+
+
+// Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow.
+Return<EvsResult> EvsCamera::doneWithFrame_1_1(const BufferDesc_1_1& bufDesc) {
+ std::lock_guard <std::mutex> lock(mAccessLock);
+ returnBuffer(bufDesc.bufferId, bufDesc.buffer.nativeHandle);
+
+ return EvsResult::OK;
+}
+
+
+Return<EvsResult> EvsCamera::pauseVideoStream() {
+ // Default implementation does not support this.
+ return EvsResult::UNDERLYING_SERVICE_ERROR;
+}
+
+
+Return<EvsResult> EvsCamera::resumeVideoStream() {
+ // Default implementation does not support this.
+ return EvsResult::UNDERLYING_SERVICE_ERROR;
+}
+
+
+bool EvsCamera::setAvailableFrames_Locked(unsigned bufferCount) {
+ if (bufferCount < 1) {
+ ALOGE("Ignoring request to set buffer count to zero");
+ return false;
+ }
+ if (bufferCount > MAX_BUFFERS_IN_FLIGHT) {
+ 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.emplace_back(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<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
+ 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_1_1 newBuffer = {};
+ AHardwareBuffer_Desc* pDesc =
+ reinterpret_cast<AHardwareBuffer_Desc *>(&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;
+
+ // Write test data into the image buffer
+ fillTestFrame(newBuffer);
+
+ // Issue the (asynchronous) callback to the client -- can't be holding the lock
+ EvsEvent event;
+ event.buffer(newBuffer);
+ auto result = mStream->notifyEvent(event);
+ 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<std::mutex> lock(mAccessLock);
+ mBuffers[idx].inUse = false;
+ mFramesInUse--;
+
+ break;
+ }
+ }
+
+ // We arbitrarily choose to generate frames at 12 fps to ensure we pass the 10fps test requirement
+ static const int kTargetFrameRate = 12;
+ static const nsecs_t kTargetFrameTimeUs = 1000*1000 / kTargetFrameRate;
+ const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ 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
+ EvsEvent event;
+ event.info(EvsEventType::STREAM_STOPPED);
+ auto result = mStream->notifyEvent(event);
+ if (!result.isOk()) {
+ ALOGE("Error delivering end of stream marker");
+ }
+
+ return;
+}
+
+
+void EvsCamera::fillTestFrame(const BufferDesc_1_1& buff) {
+ // Lock our output buffer for writing
+ uint32_t *pixels = nullptr;
+ const AHardwareBuffer_Desc* pDesc =
+ reinterpret_cast<const AHardwareBuffer_Desc *>(&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");
+ }
+
+ // Fill in the test pixels
+ for (unsigned row = 0; row < pDesc->height; row++) {
+ for (unsigned col = 0; col < pDesc->width; col++) {
+ // Index into the row to check the pixel at this column.
+ // We expect 0xFF in the LSB channel, a vertical gradient in the
+ // second channel, a horitzontal gradient in the third channel, and
+ // 0xFF in the MSB.
+ // The exception is the very first 32 bits which is used for the
+ // time varying frame signature to avoid getting fooled by a static image.
+ uint32_t expectedPixel = 0xFF0000FF | // MSB and LSB
+ ((row & 0xFF) << 8) | // vertical gradient
+ ((col & 0xFF) << 16); // horizontal gradient
+ if ((row | col) == 0) {
+ static uint32_t sFrameTicker = 0;
+ expectedPixel = (sFrameTicker) & 0xFF;
+ sFrameTicker++;
+ }
+ pixels[col] = expectedPixel;
+ }
+ // 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 BufferDesc_1_0& buff) {
+ BufferDesc_1_1 newBufDesc = {};
+ AHardwareBuffer_Desc desc = {
+ 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
+ };
+ memcpy(&desc, &newBufDesc.buffer.description, sizeof(desc));
+ newBufDesc.buffer.nativeHandle = buff.memHandle;
+ newBufDesc.pixelSize = buff.pixelSize;
+ newBufDesc.bufferId = buff.bufferId;
+
+ return fillTestFrame(newBufDesc);
+}
+
+
+void EvsCamera::returnBuffer(const uint32_t bufferId, const buffer_handle_t memHandle) {
+ std::lock_guard <std::mutex> lock(mAccessLock);
+
+ 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;
+ }
+ }
+ }
+ }
+}
+
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace evs
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/automotive/evs/1.1/default/EvsCamera.h b/automotive/evs/1.1/default/EvsCamera.h
new file mode 100644
index 0000000..0982464
--- /dev/null
+++ b/automotive/evs/1.1/default/EvsCamera.h
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERA_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERA_H
+
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <ui/GraphicBuffer.h>
+
+#include <thread>
+
+using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+using IEvsCameraStream_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCameraStream;
+using IEvsCameraStream_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCameraStream;
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using ::android::hardware::automotive::evs::V1_0::CameraDesc;
+
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+// From EvsEnumerator.h
+class EvsEnumerator;
+
+
+class EvsCamera : public IEvsCamera {
+public:
+ // Methods from ::android::hardware::automotive::evs::V1_0::IEvsCamera follow.
+ Return<void> getCameraInfo(getCameraInfo_cb _hidl_cb) override;
+ Return<EvsResult> setMaxFramesInFlight(uint32_t bufferCount) override;
+ Return<EvsResult> startVideoStream(const ::android::sp<IEvsCameraStream_1_0>& stream) override;
+ Return<void> stopVideoStream() override;
+ Return<void> doneWithFrame(const BufferDesc_1_0& buffer) override;
+
+ Return<int32_t> getExtendedInfo(uint32_t opaqueIdentifier) override;
+ Return<EvsResult> setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue) override;
+
+ // Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow.
+ Return<EvsResult> pauseVideoStream() override;
+ Return<EvsResult> resumeVideoStream() override;
+ Return<EvsResult> doneWithFrame_1_1(const BufferDesc_1_1& buffer) override;
+
+ // Implementation details
+ EvsCamera(const char *id);
+ virtual ~EvsCamera() override;
+ void forceShutdown(); // This gets called if another caller "steals" ownership of the camera
+
+ const CameraDesc& getDesc() { return mDescription; };
+
+ static const char kCameraName_Backup[];
+
+private:
+ // These three functions are expected to be called while mAccessLock is held
+ bool setAvailableFrames_Locked(unsigned bufferCount);
+ unsigned increaseAvailableFrames_Locked(unsigned numToAdd);
+ unsigned decreaseAvailableFrames_Locked(unsigned numToRemove);
+
+ void generateFrames();
+ void fillTestFrame(const BufferDesc_1_0& buff);
+ void fillTestFrame(const BufferDesc_1_1& buff);
+ void returnBuffer(const uint32_t bufferId, const buffer_handle_t memHandle);
+
+ sp<EvsEnumerator> mEnumerator; // The enumerator object that created this camera
+
+ CameraDesc mDescription = {}; // The properties of this camera
+
+ std::thread mCaptureThread; // The thread we'll use to synthesize frames
+
+ uint32_t mWidth = 0; // Horizontal pixel count in the buffers
+ uint32_t mHeight = 0; // Vertical pixel count in the buffers
+ uint32_t mFormat = 0; // Values from android_pixel_format_t
+ uint64_t mUsage = 0; // Values from from Gralloc.h
+ uint32_t mStride = 0; // Bytes per line in the buffers
+
+ sp<IEvsCameraStream_1_1> mStream = nullptr; // The callback used to deliver each frame
+
+ struct BufferRecord {
+ buffer_handle_t handle;
+ bool inUse;
+
+ explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false) {};
+ };
+
+ std::vector <BufferRecord> mBuffers; // Graphics buffers to transfer images
+ unsigned mFramesAllowed; // How many buffers are we currently using
+ unsigned mFramesInUse; // How many buffers are currently outstanding
+
+ enum StreamStateValues {
+ STOPPED,
+ RUNNING,
+ STOPPING,
+ DEAD,
+ };
+ StreamStateValues mStreamState;
+
+ // Synchronization necessary to deconflict mCaptureThread from the main service thread
+ std::mutex mAccessLock;
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERA_H
diff --git a/automotive/evs/1.1/default/EvsDisplay.cpp b/automotive/evs/1.1/default/EvsDisplay.cpp
new file mode 100644
index 0000000..74c099a
--- /dev/null
+++ b/automotive/evs/1.1/default/EvsDisplay.cpp
@@ -0,0 +1,335 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.automotive.evs@1.1-service"
+
+#include "EvsDisplay.h"
+
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+EvsDisplay::EvsDisplay() {
+ ALOGD("EvsDisplay instantiated");
+
+ // Set up our self description
+ // NOTE: These are arbitrary values chosen for testing
+ mInfo.displayId = "Mock Display";
+ mInfo.vendorFlags = 3870;
+
+ // Assemble the buffer description we'll use for our render target
+ mBuffer.width = 320;
+ mBuffer.height = 240;
+ mBuffer.format = HAL_PIXEL_FORMAT_RGBA_8888;
+ mBuffer.usage = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER;
+ mBuffer.bufferId = 0x3870; // Arbitrary magic number for self recognition
+ mBuffer.pixelSize = 4;
+}
+
+
+EvsDisplay::~EvsDisplay() {
+ ALOGD("EvsDisplay being destroyed");
+ forceShutdown();
+}
+
+
+/**
+ * This gets called if another caller "steals" ownership of the display
+ */
+void EvsDisplay::forceShutdown()
+{
+ ALOGD("EvsDisplay forceShutdown");
+ std::lock_guard<std::mutex> lock(mAccessLock);
+
+ // If the buffer isn't being held by a remote client, release it now as an
+ // optimization to release the resources more quickly than the destructor might
+ // get called.
+ if (mBuffer.memHandle) {
+ // Report if we're going away while a buffer is outstanding
+ if (mFrameBusy) {
+ ALOGE("EvsDisplay going down while client is holding a buffer");
+ }
+
+ // Drop the graphics buffer we've been using
+ GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
+ alloc.free(mBuffer.memHandle);
+ mBuffer.memHandle = nullptr;
+ }
+
+ // Put this object into an unrecoverable error state since somebody else
+ // is going to own the display now.
+ mRequestedState = DisplayState::DEAD;
+}
+
+
+/**
+ * Returns basic information about the EVS display provided by the system.
+ * See the description of the DisplayDesc structure for details.
+ */
+Return<void> EvsDisplay::getDisplayInfo(getDisplayInfo_cb _hidl_cb) {
+ ALOGD("getDisplayInfo");
+
+ // Send back our self description
+ _hidl_cb(mInfo);
+ return Void();
+}
+
+
+/**
+ * Clients may set the display state to express their desired state.
+ * The HAL implementation must gracefully accept a request for any state
+ * while in any other state, although the response may be to ignore the request.
+ * The display is defined to start in the NOT_VISIBLE state upon initialization.
+ * The client is then expected to request the VISIBLE_ON_NEXT_FRAME state, and
+ * then begin providing video. When the display is no longer required, the client
+ * is expected to request the NOT_VISIBLE state after passing the last video frame.
+ */
+Return<EvsResult> EvsDisplay::setDisplayState(DisplayState state) {
+ ALOGD("setDisplayState");
+ std::lock_guard<std::mutex> lock(mAccessLock);
+
+ if (mRequestedState == DisplayState::DEAD) {
+ // This object no longer owns the display -- it's been superceeded!
+ return EvsResult::OWNERSHIP_LOST;
+ }
+
+ // Ensure we recognize the requested state so we don't go off the rails
+ if (state < DisplayState::NUM_STATES) {
+ // Record the requested state
+ mRequestedState = state;
+ return EvsResult::OK;
+ }
+ else {
+ // Turn off the display if asked for an unrecognized state
+ mRequestedState = DisplayState::NOT_VISIBLE;
+ return EvsResult::INVALID_ARG;
+ }
+}
+
+
+/**
+ * The HAL implementation should report the actual current state, which might
+ * transiently differ from the most recently requested state. Note, however, that
+ * the logic responsible for changing display states should generally live above
+ * the device layer, making it undesirable for the HAL implementation to
+ * spontaneously change display states.
+ */
+Return<DisplayState> EvsDisplay::getDisplayState() {
+ ALOGD("getDisplayState");
+ std::lock_guard<std::mutex> lock(mAccessLock);
+
+ return mRequestedState;
+}
+
+
+/**
+ * This call returns a handle to a frame buffer associated with the display.
+ * This buffer may be locked and written to by software and/or GL. This buffer
+ * must be returned via a call to returnTargetBufferForDisplay() even if the
+ * display is no longer visible.
+ */
+// TODO: We need to know if/when our client dies so we can get the buffer back! (blocked b/31632518)
+Return<void> EvsDisplay::getTargetBuffer(getTargetBuffer_cb _hidl_cb) {
+ ALOGD("getTargetBuffer");
+ std::lock_guard<std::mutex> lock(mAccessLock);
+
+ if (mRequestedState == DisplayState::DEAD) {
+ ALOGE("Rejecting buffer request from object that lost ownership of the display.");
+ BufferDesc_1_0 nullBuff = {};
+ _hidl_cb(nullBuff);
+ return Void();
+ }
+
+ // If we don't already have a buffer, allocate one now
+ if (!mBuffer.memHandle) {
+ // Allocate the buffer that will hold our displayable image
+ buffer_handle_t handle = nullptr;
+ GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
+ status_t result = alloc.allocate(
+ mBuffer.width, mBuffer.height, mBuffer.format, 1, mBuffer.usage,
+ &handle, &mBuffer.stride, 0, "EvsDisplay");
+ if (result != NO_ERROR) {
+ ALOGE("Error %d allocating %d x %d graphics buffer",
+ result, mBuffer.width, mBuffer.height);
+ BufferDesc_1_0 nullBuff = {};
+ _hidl_cb(nullBuff);
+ return Void();
+ }
+ if (!handle) {
+ ALOGE("We didn't get a buffer handle back from the allocator");
+ BufferDesc_1_0 nullBuff = {};
+ _hidl_cb(nullBuff);
+ return Void();
+ }
+
+ mBuffer.memHandle = handle;
+ mFrameBusy = false;
+ ALOGD("Allocated new buffer %p with stride %u",
+ mBuffer.memHandle.getNativeHandle(), mBuffer.stride);
+ }
+
+ // Do we have a frame available?
+ if (mFrameBusy) {
+ // This means either we have a 2nd client trying to compete for buffers
+ // (an unsupported mode of operation) or else the client hasn't returned
+ // a previously issued buffer yet (they're behaving badly).
+ // NOTE: We have to make the callback even if we have nothing to provide
+ ALOGE("getTargetBuffer called while no buffers available.");
+ BufferDesc_1_0 nullBuff = {};
+ _hidl_cb(nullBuff);
+ return Void();
+ } else {
+ // Mark our buffer as busy
+ mFrameBusy = true;
+
+ // Send the buffer to the client
+ ALOGD("Providing display buffer handle %p as id %d",
+ mBuffer.memHandle.getNativeHandle(), mBuffer.bufferId);
+ _hidl_cb(mBuffer);
+ return Void();
+ }
+}
+
+
+/**
+ * This call tells the display that the buffer is ready for display.
+ * The buffer is no longer valid for use by the client after this call.
+ */
+Return<EvsResult> EvsDisplay::returnTargetBufferForDisplayImpl(const uint32_t bufferId, const buffer_handle_t memHandle) {
+ ALOGD("returnTargetBufferForDisplay %p", memHandle);
+ std::lock_guard<std::mutex> lock(mAccessLock);
+
+ // Nobody should call us with a null handle
+ if (!memHandle) {
+ ALOGE ("returnTargetBufferForDisplay called without a valid buffer handle.\n");
+ return EvsResult::INVALID_ARG;
+ }
+ if (bufferId != mBuffer.bufferId) {
+ ALOGE ("Got an unrecognized frame returned.\n");
+ return EvsResult::INVALID_ARG;
+ }
+ if (!mFrameBusy) {
+ ALOGE ("A frame was returned with no outstanding frames.\n");
+ return EvsResult::BUFFER_NOT_AVAILABLE;
+ }
+
+ mFrameBusy = false;
+
+ // If we've been displaced by another owner of the display, then we can't do anything else
+ if (mRequestedState == DisplayState::DEAD) {
+ return EvsResult::OWNERSHIP_LOST;
+ }
+
+ // If we were waiting for a new frame, this is it!
+ if (mRequestedState == DisplayState::VISIBLE_ON_NEXT_FRAME) {
+ mRequestedState = DisplayState::VISIBLE;
+ }
+
+ // Validate we're in an expected state
+ if (mRequestedState != DisplayState::VISIBLE) {
+ // We shouldn't get frames back when we're not visible.
+ ALOGE ("Got an unexpected frame returned while not visible - ignoring.\n");
+ } else {
+ // This is where the buffer would be made visible.
+ // For now we simply validate it has the data we expect in it by reading it back
+
+ // Lock our display buffer for reading
+ uint32_t* pixels = nullptr;
+ GraphicBufferMapper &mapper = GraphicBufferMapper::get();
+ mapper.lock(mBuffer.memHandle,
+ GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER,
+ android::Rect(mBuffer.width, mBuffer.height),
+ (void **)&pixels);
+
+ // If we failed to lock the pixel buffer, we're about to crash, but log it first
+ if (!pixels) {
+ ALOGE("Display failed to gain access to image buffer for reading");
+ }
+
+ // Check the test pixels
+ bool frameLooksGood = true;
+ for (unsigned row = 0; row < mBuffer.height; row++) {
+ for (unsigned col = 0; col < mBuffer.width; col++) {
+ // Index into the row to check the pixel at this column.
+ // We expect 0xFF in the LSB channel, a vertical gradient in the
+ // second channel, a horitzontal gradient in the third channel, and
+ // 0xFF in the MSB.
+ // The exception is the very first 32 bits which is used for the
+ // time varying frame signature to avoid getting fooled by a static image.
+ uint32_t expectedPixel = 0xFF0000FF | // MSB and LSB
+ ((row & 0xFF) << 8) | // vertical gradient
+ ((col & 0xFF) << 16); // horizontal gradient
+ if ((row | col) == 0) {
+ // we'll check the "uniqueness" of the frame signature below
+ continue;
+ }
+ // Walk across this row (we'll step rows below)
+ uint32_t receivedPixel = pixels[col];
+ if (receivedPixel != expectedPixel) {
+ ALOGE("Pixel check mismatch in frame buffer");
+ frameLooksGood = false;
+ break;
+ }
+ }
+
+ if (!frameLooksGood) {
+ break;
+ }
+
+ // Point to the next row (NOTE: gralloc reports stride in units of pixels)
+ pixels = pixels + mBuffer.stride;
+ }
+
+ // Ensure we don't see the same buffer twice without it being rewritten
+ static uint32_t prevSignature = ~0;
+ uint32_t signature = pixels[0] & 0xFF;
+ if (prevSignature == signature) {
+ frameLooksGood = false;
+ ALOGE("Duplicate, likely stale frame buffer detected");
+ }
+
+
+ // Release our output buffer
+ mapper.unlock(mBuffer.memHandle);
+
+ if (!frameLooksGood) {
+ return EvsResult::UNDERLYING_SERVICE_ERROR;
+ }
+ }
+
+ return EvsResult::OK;
+}
+
+
+Return<EvsResult> EvsDisplay::returnTargetBufferForDisplay(const BufferDesc_1_0& buffer) {
+ return returnTargetBufferForDisplayImpl(buffer.bufferId, buffer.memHandle);
+}
+
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/automotive/evs/1.1/default/EvsDisplay.h b/automotive/evs/1.1/default/EvsDisplay.h
new file mode 100644
index 0000000..2a56535
--- /dev/null
+++ b/automotive/evs/1.1/default/EvsDisplay.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSDISPLAY_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSDISPLAY_H
+
+#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
+#include <ui/GraphicBuffer.h>
+
+using ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using ::android::hardware::automotive::evs::V1_0::DisplayDesc;
+using ::android::hardware::automotive::evs::V1_0::DisplayState;
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+class EvsDisplay : public IEvsDisplay {
+public:
+ // Methods from ::android::hardware::automotive::evs::V1_0::IEvsDisplay follow.
+ Return<void> getDisplayInfo(getDisplayInfo_cb _hidl_cb) override;
+ Return<EvsResult> setDisplayState(DisplayState state) override;
+ Return<DisplayState> getDisplayState() override;
+ Return<void> getTargetBuffer(getTargetBuffer_cb _hidl_cb) override;
+ Return<EvsResult> returnTargetBufferForDisplay(const BufferDesc_1_0& buffer) override;
+
+
+ // Implementation details
+ EvsDisplay();
+ virtual ~EvsDisplay() override;
+
+ void forceShutdown(); // This gets called if another caller "steals" ownership of the display
+ Return<EvsResult> returnTargetBufferForDisplayImpl(const uint32_t bufferId,
+ const buffer_handle_t memHandle);
+
+private:
+ DisplayDesc mInfo = {};
+ BufferDesc_1_0 mBuffer = {}; // A graphics buffer into which we'll store images
+
+ bool mFrameBusy = false; // A flag telling us our buffer is in use
+ DisplayState mRequestedState = DisplayState::NOT_VISIBLE;
+
+ std::mutex mAccessLock;
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSDISPLAY_H
diff --git a/automotive/evs/1.1/default/EvsEnumerator.cpp b/automotive/evs/1.1/default/EvsEnumerator.cpp
new file mode 100644
index 0000000..b324907
--- /dev/null
+++ b/automotive/evs/1.1/default/EvsEnumerator.cpp
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.automotive.evs@1.1-service"
+
+#include "EvsEnumerator.h"
+#include "EvsCamera.h"
+#include "EvsDisplay.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+// NOTE: All members values are static so that all clients operate on the same state
+// That is to say, this is effectively a singleton despite the fact that HIDL
+// constructs a new instance for each client.
+std::list<EvsEnumerator::CameraRecord> EvsEnumerator::sCameraList;
+wp<EvsDisplay> EvsEnumerator::sActiveDisplay;
+
+
+EvsEnumerator::EvsEnumerator() {
+ ALOGD("EvsEnumerator created");
+
+ // Add sample camera data to our list of cameras
+ // In a real driver, this would be expected to can the available hardware
+ sCameraList.emplace_back(EvsCamera::kCameraName_Backup);
+ sCameraList.emplace_back("LaneView");
+ sCameraList.emplace_back("right turn");
+}
+
+
+// Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow.
+Return<void> EvsEnumerator::getCameraList(getCameraList_cb _hidl_cb) {
+ ALOGD("getCameraList");
+
+ const unsigned numCameras = sCameraList.size();
+
+ // Build up a packed array of CameraDesc for return
+ // NOTE: Only has to live until the callback returns
+ std::vector<CameraDesc_1_0> descriptions;
+ descriptions.reserve(numCameras);
+ for (const auto& cam : sCameraList) {
+ descriptions.push_back( cam.desc );
+ }
+
+ // Encapsulate our camera descriptions in the HIDL vec type
+ hidl_vec<CameraDesc_1_0> hidlCameras(descriptions);
+
+ // Send back the results
+ ALOGD("reporting %zu cameras available", hidlCameras.size());
+ _hidl_cb(hidlCameras);
+
+ // HIDL convention says we return Void if we sent our result back via callback
+ return Void();
+}
+
+
+Return<sp<IEvsCamera_1_0>> EvsEnumerator::openCamera(const hidl_string& cameraId) {
+ ALOGD("openCamera");
+
+ // Find the named camera
+ CameraRecord *pRecord = nullptr;
+ for (auto &&cam : sCameraList) {
+ if (cam.desc.cameraId == cameraId) {
+ // Found a match!
+ pRecord = &cam;
+ break;
+ }
+ }
+
+ // Is this a recognized camera id?
+ if (!pRecord) {
+ ALOGE("Requested camera %s not found", cameraId.c_str());
+ return nullptr;
+ }
+
+ // Has this camera already been instantiated by another caller?
+ sp<EvsCamera> pActiveCamera = pRecord->activeInstance.promote();
+ if (pActiveCamera != nullptr) {
+ ALOGW("Killing previous camera because of new caller");
+ closeCamera(pActiveCamera);
+ }
+
+ // Construct a camera instance for the caller
+ pActiveCamera = new EvsCamera(cameraId.c_str());
+ pRecord->activeInstance = pActiveCamera;
+ if (pActiveCamera == nullptr) {
+ ALOGE("Failed to allocate new EvsCamera object for %s\n", cameraId.c_str());
+ }
+
+ return pActiveCamera;
+}
+
+
+Return<void> EvsEnumerator::closeCamera(const ::android::sp<IEvsCamera_1_0>& pCamera) {
+ ALOGD("closeCamera");
+
+ auto pCamera_1_1 = IEvsCamera_1_1::castFrom(pCamera).withDefault(nullptr);
+ if (pCamera_1_1 == nullptr) {
+ ALOGE("Ignoring call to closeCamera with null camera ptr");
+ return Void();
+ }
+
+ // Get the camera id so we can find it in our list
+ std::string cameraId;
+ pCamera_1_1->getCameraInfo([&cameraId](CameraDesc desc) {
+ cameraId = desc.cameraId;
+ }
+ );
+
+ // Find the named camera
+ CameraRecord *pRecord = nullptr;
+ for (auto &&cam : sCameraList) {
+ if (cam.desc.cameraId == cameraId) {
+ // Found a match!
+ pRecord = &cam;
+ break;
+ }
+ }
+
+ // Is the display being destroyed actually the one we think is active?
+ if (!pRecord) {
+ ALOGE("Asked to close a camera who's name isn't recognized");
+ } else {
+ sp<EvsCamera> pActiveCamera = pRecord->activeInstance.promote();
+
+ if (pActiveCamera == nullptr) {
+ ALOGE("Somehow a camera is being destroyed when the enumerator didn't know one existed");
+ } else if (pActiveCamera != pCamera_1_1) {
+ // This can happen if the camera was aggressively reopened, orphaning this previous instance
+ ALOGW("Ignoring close of previously orphaned camera - why did a client steal?");
+ } else {
+ // Drop the active camera
+ pActiveCamera->forceShutdown();
+ pRecord->activeInstance = nullptr;
+ }
+ }
+
+ return Void();
+}
+
+
+Return<sp<IEvsDisplay>> EvsEnumerator::openDisplay() {
+ ALOGD("openDisplay");
+
+ // If we already have a display active, then we need to shut it down so we can
+ // give exclusive access to the new caller.
+ sp<EvsDisplay> pActiveDisplay = sActiveDisplay.promote();
+ if (pActiveDisplay != nullptr) {
+ ALOGW("Killing previous display because of new caller");
+ closeDisplay(pActiveDisplay);
+ }
+
+ // Create a new display interface and return it
+ pActiveDisplay = new EvsDisplay();
+ sActiveDisplay = pActiveDisplay;
+
+ ALOGD("Returning new EvsDisplay object %p", pActiveDisplay.get());
+ return pActiveDisplay;
+}
+
+
+Return<void> EvsEnumerator::closeDisplay(const ::android::sp<IEvsDisplay>& pDisplay) {
+ ALOGD("closeDisplay");
+
+ // Do we still have a display object we think should be active?
+ sp<EvsDisplay> pActiveDisplay = sActiveDisplay.promote();
+ if (pActiveDisplay == nullptr) {
+ ALOGE("Somehow a display is being destroyed when the enumerator didn't know one existed");
+ } else if (sActiveDisplay != pDisplay) {
+ ALOGW("Ignoring close of previously orphaned display - why did a client steal?");
+ } else {
+ // Drop the active display
+ pActiveDisplay->forceShutdown();
+ sActiveDisplay = nullptr;
+ }
+
+ return Void();
+}
+
+
+Return<DisplayState> EvsEnumerator::getDisplayState() {
+ ALOGD("getDisplayState");
+
+ // Do we still have a display object we think should be active?
+ sp<IEvsDisplay> pActiveDisplay = sActiveDisplay.promote();
+ if (pActiveDisplay != nullptr) {
+ return pActiveDisplay->getDisplayState();
+ } else {
+ return DisplayState::NOT_OPEN;
+ }
+}
+
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/automotive/evs/1.1/default/EvsEnumerator.h b/automotive/evs/1.1/default/EvsEnumerator.h
new file mode 100644
index 0000000..11c2170
--- /dev/null
+++ b/automotive/evs/1.1/default/EvsEnumerator.h
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
+#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
+
+#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+
+#include <list>
+
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using ::android::hardware::automotive::evs::V1_0::DisplayState;
+using ::android::hardware::automotive::evs::V1_0::IEvsEnumerator;
+using IEvsCamera_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCamera;
+using IEvsCamera_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCamera;
+using CameraDesc_1_0 = ::android::hardware::automotive::evs::V1_0::CameraDesc;
+
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+
+class EvsCamera; // from EvsCamera.h
+class EvsDisplay; // from EvsDisplay.h
+
+
+class EvsEnumerator : public IEvsEnumerator {
+public:
+ // Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow.
+ Return<void> getCameraList(getCameraList_cb _hidl_cb) override;
+ Return<sp<IEvsCamera_1_0>> openCamera(const hidl_string& cameraId) override;
+ Return<void> closeCamera(const ::android::sp<IEvsCamera_1_0>& carCamera) override;
+ Return<sp<IEvsDisplay>> openDisplay() override;
+ Return<void> closeDisplay(const ::android::sp<IEvsDisplay>& display) override;
+ Return<DisplayState> getDisplayState() override;
+
+ // Implementation details
+ EvsEnumerator();
+
+private:
+ // NOTE: All members values are static so that all clients operate on the same state
+ // That is to say, this is effectively a singleton despite the fact that HIDL
+ // constructs a new instance for each client.
+ struct CameraRecord {
+ CameraDesc_1_0 desc;
+ wp<EvsCamera> activeInstance;
+
+ CameraRecord(const char *cameraId) : desc() { desc.cameraId = cameraId; }
+ };
+ static std::list<CameraRecord> sCameraList;
+
+ static wp<EvsDisplay> sActiveDisplay; // Weak pointer. Object destructs if client dies.
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
diff --git a/automotive/evs/1.1/default/ServiceNames.h b/automotive/evs/1.1/default/ServiceNames.h
new file mode 100644
index 0000000..1178da5
--- /dev/null
+++ b/automotive/evs/1.1/default/ServiceNames.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+const static char kEnumeratorServiceName[] = "EvsEnumeratorHw";
diff --git a/automotive/evs/1.1/default/android.hardware.automotive.evs@1.1-service.rc b/automotive/evs/1.1/default/android.hardware.automotive.evs@1.1-service.rc
new file mode 100644
index 0000000..284b3fd
--- /dev/null
+++ b/automotive/evs/1.1/default/android.hardware.automotive.evs@1.1-service.rc
@@ -0,0 +1,5 @@
+service vendor.evs-hal-mock /vendor/bin/hw/android.hardware.automotive.evs@1.1-service
+ class hal
+ user automotive_evs
+ group automotive_evs
+ disabled
diff --git a/automotive/evs/1.1/default/service.cpp b/automotive/evs/1.1/default/service.cpp
new file mode 100644
index 0000000..128a14a
--- /dev/null
+++ b/automotive/evs/1.1/default/service.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.automotive.evs@1.1-service"
+
+#include <unistd.h>
+
+#include <hidl/HidlTransportSupport.h>
+#include <log/log.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+
+#include "ServiceNames.h"
+#include "EvsEnumerator.h"
+#include "EvsDisplay.h"
+
+
+// libhidl:
+using android::hardware::configureRpcThreadpool;
+using android::hardware::joinRpcThreadpool;
+
+// Generated HIDL files
+using android::hardware::automotive::evs::V1_0::IEvsEnumerator;
+using android::hardware::automotive::evs::V1_0::IEvsDisplay;
+
+// The namespace in which all our implementation code lives
+using namespace android::hardware::automotive::evs::V1_1::implementation;
+using namespace android;
+
+
+int main() {
+ ALOGI("EVS Hardware Enumerator service is starting");
+ android::sp<IEvsEnumerator> service = new EvsEnumerator();
+
+ configureRpcThreadpool(1, true /* callerWillJoin */);
+
+ // Register our service -- if somebody is already registered by our name,
+ // they will be killed (their thread pool will throw an exception).
+ status_t status = service->registerAsService(kEnumeratorServiceName);
+ if (status == OK) {
+ ALOGD("%s is ready.", kEnumeratorServiceName);
+ joinRpcThreadpool();
+ } else {
+ ALOGE("Could not register service %s (%d).", kEnumeratorServiceName, status);
+ }
+
+ // In normal operation, we don't expect the thread pool to exit
+ ALOGE("EVS Hardware Enumerator is shutting down");
+ return 1;
+}
diff --git a/automotive/evs/1.1/types.hal b/automotive/evs/1.1/types.hal
new file mode 100644
index 0000000..ff6ab4e
--- /dev/null
+++ b/automotive/evs/1.1/types.hal
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+package android.hardware.automotive.evs@1.1;
+
+import @1.0::CameraDesc;
+import @1.0::DisplayDesc;
+import @1.0::DisplayState;
+import @1.0::EvsResult;
+import android.hardware.graphics.common@1.2::HardwareBuffer;
+
+/**
+ * Structure representing an image buffer through our APIs
+ *
+ * In addition to the handle to the graphics memory, we need to retain
+ * the properties of the buffer for easy reference and reconstruction of
+ * an ANativeWindowBuffer object on the remote side of API calls.
+ * (Not least because OpenGL expect an ANativeWindowBuffer* for us as a
+ * texture via eglCreateImageKHR().
+ */
+struct BufferDesc {
+ /**
+ * HIDL counterpart of `AHardwareBuffer_Desc`. Please see
+ * hardware/interfaces/graphics/common/1.2/types.hal for more details.
+ */
+ HardwareBuffer buffer;
+ /**
+ * The size of a pixel in the units of bytes
+ */
+ uint32_t pixelSize;
+ /**
+ * Opaque value from driver
+ */
+ uint32_t bufferId;
+};
+
+/**
+ * EVS event types
+ */
+enum EvsEventType : uint32_t {
+ /**
+ * Video stream is started
+ */
+ STREAM_STARTED = 0,
+ /**
+ * Video stream is stopped
+ */
+ STREAM_STOPPED,
+ /**
+ * Video frame is dropped
+ */
+ FRAME_DROPPED,
+ /**
+ * Timeout happens
+ */
+ TIMEOUT,
+};
+
+/**
+ * EVS event definition
+ */
+safe_union EvsEvent {
+ /**
+ * A buffer descriptor of an image frame
+ */
+ BufferDesc buffer;
+ /**
+ * General streaming events
+ */
+ EvsEventType info;
+};
diff --git a/automotive/evs/1.1/vts/functional/Android.bp b/automotive/evs/1.1/vts/functional/Android.bp
new file mode 100644
index 0000000..55c50a4
--- /dev/null
+++ b/automotive/evs/1.1/vts/functional/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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.
+//
+
+cc_test {
+ name: "VtsHalEvsV1_1TargetTest",
+ srcs: [
+ "FrameHandler.cpp",
+ "VtsHalEvsV1_1TargetTest.cpp",
+ ],
+ defaults: ["VtsHalTargetTestDefaults"],
+ shared_libs: [
+ "libui",
+ ],
+ static_libs: [
+ "android.hardware.automotive.evs@1.0",
+ "android.hardware.automotive.evs@1.1",
+ "android.hardware.automotive.evs@common-default-lib",
+ "android.hardware.graphics.common@1.0",
+ "android.hardware.graphics.common@1.1",
+ "android.hardware.graphics.common@1.2",
+ ],
+ test_suites: ["general-tests"],
+ cflags: [
+ "-O0",
+ "-g",
+ ],
+}
diff --git a/automotive/evs/1.1/vts/functional/FrameHandler.cpp b/automotive/evs/1.1/vts/functional/FrameHandler.cpp
new file mode 100644
index 0000000..b7c7f21
--- /dev/null
+++ b/automotive/evs/1.1/vts/functional/FrameHandler.cpp
@@ -0,0 +1,340 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VtsHalEvsTest"
+
+#include "FrameHandler.h"
+#include "FormatConvert.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <android/log.h>
+#include <cutils/native_handle.h>
+#include <ui/GraphicBuffer.h>
+
+FrameHandler::FrameHandler(android::sp <IEvsCamera> pCamera, CameraDesc cameraInfo,
+ android::sp <IEvsDisplay> pDisplay,
+ BufferControlFlag mode) :
+ mCamera(pCamera),
+ mCameraInfo(cameraInfo),
+ mDisplay(pDisplay),
+ mReturnMode(mode) {
+ // Nothing but member initialization here...
+}
+
+
+void FrameHandler::shutdown()
+{
+ // Make sure we're not still streaming
+ blockingStopStream();
+
+ // At this point, the receiver thread is no longer running, so we can safely drop
+ // our remote object references so they can be freed
+ mCamera = nullptr;
+ mDisplay = nullptr;
+}
+
+
+bool FrameHandler::startStream() {
+ // Tell the camera to start streaming
+ Return<EvsResult> result = mCamera->startVideoStream(this);
+ if (result != EvsResult::OK) {
+ return false;
+ }
+
+ // Mark ourselves as running
+ mLock.lock();
+ mRunning = true;
+ mLock.unlock();
+
+ return true;
+}
+
+
+void FrameHandler::asyncStopStream() {
+ // Tell the camera to stop streaming.
+ // This will result in a null frame being delivered when the stream actually stops.
+ mCamera->stopVideoStream();
+}
+
+
+void FrameHandler::blockingStopStream() {
+ // Tell the stream to stop
+ asyncStopStream();
+
+ // Wait until the stream has actually stopped
+ std::unique_lock<std::mutex> lock(mLock);
+ if (mRunning) {
+ mSignal.wait(lock, [this]() { return !mRunning; });
+ }
+}
+
+
+bool FrameHandler::returnHeldBuffer() {
+ std::unique_lock<std::mutex> lock(mLock);
+
+ // Return the oldest buffer we're holding
+ if (mHeldBuffers.empty()) {
+ // No buffers are currently held
+ return false;
+ }
+
+ BufferDesc_1_1 buffer = mHeldBuffers.front();
+ mHeldBuffers.pop();
+ mCamera->doneWithFrame_1_1(buffer);
+
+ return true;
+}
+
+
+bool FrameHandler::isRunning() {
+ std::unique_lock<std::mutex> lock(mLock);
+ return mRunning;
+}
+
+
+void FrameHandler::waitForFrameCount(unsigned frameCount) {
+ // Wait until we've seen at least the requested number of frames (could be more)
+ std::unique_lock<std::mutex> lock(mLock);
+ mSignal.wait(lock, [this, frameCount](){ return mFramesReceived >= frameCount; });
+}
+
+
+void FrameHandler::getFramesCounters(unsigned* received, unsigned* displayed) {
+ std::unique_lock<std::mutex> lock(mLock);
+
+ if (received) {
+ *received = mFramesReceived;
+ }
+ if (displayed) {
+ *displayed = mFramesDisplayed;
+ }
+}
+
+
+Return<void> FrameHandler::deliverFrame(const BufferDesc_1_0& bufferArg) {
+ ALOGW("A frame delivered via v1.0 method is rejected.");
+ mCamera->doneWithFrame(bufferArg);
+ return Void();
+}
+
+
+Return<void> FrameHandler::notifyEvent(const EvsEvent& event) {
+ // Local flag we use to keep track of when the stream is stopping
+ bool timeToStop = false;
+
+ auto type = event.getDiscriminator();
+ if (type == EvsEvent::hidl_discriminator::info) {
+ if (event.info() == EvsEventType::STREAM_STOPPED) {
+ // Signal that the last frame has been received and the stream is stopped
+ timeToStop = true;
+ } else {
+ ALOGD("Received an event 0x%X", event.info());
+ }
+ } else {
+ auto bufDesc = event.buffer();
+ const AHardwareBuffer_Desc* pDesc =
+ reinterpret_cast<const AHardwareBuffer_Desc *>(&bufDesc.buffer.description);
+ ALOGD("Received a frame from the camera (%p)",
+ bufDesc.buffer.nativeHandle.getNativeHandle());
+
+ // Store a dimension of a received frame.
+ mFrameWidth = pDesc->width;
+ mFrameHeight = pDesc->height;
+
+ // If we were given an opened display at construction time, then send the received
+ // image back down the camera.
+ if (mDisplay.get()) {
+ // Get the output buffer we'll use to display the imagery
+ BufferDesc_1_0 tgtBuffer = {};
+ mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc_1_0& buff) {
+ tgtBuffer = buff;
+ }
+ );
+
+ if (tgtBuffer.memHandle == nullptr) {
+ printf("Didn't get target buffer - frame lost\n");
+ ALOGE("Didn't get requested output buffer -- skipping this frame.");
+ } else {
+ // Copy the contents of the of buffer.memHandle into tgtBuffer
+ copyBufferContents(tgtBuffer, bufDesc);
+
+ // Send the target buffer back for display
+ Return<EvsResult> result = mDisplay->returnTargetBufferForDisplay(tgtBuffer);
+ if (!result.isOk()) {
+ printf("HIDL error on display buffer (%s)- frame lost\n",
+ result.description().c_str());
+ ALOGE("Error making the remote function call. HIDL said %s",
+ result.description().c_str());
+ } else if (result != EvsResult::OK) {
+ printf("Display reported error - frame lost\n");
+ ALOGE("We encountered error %d when returning a buffer to the display!",
+ (EvsResult) result);
+ } else {
+ // Everything looks good!
+ // Keep track so tests or watch dogs can monitor progress
+ mLock.lock();
+ mFramesDisplayed++;
+ mLock.unlock();
+ }
+ }
+ }
+
+
+ switch (mReturnMode) {
+ case eAutoReturn:
+ // Send the camera buffer back now that the client has seen it
+ ALOGD("Calling doneWithFrame");
+ // TODO: Why is it that we get a HIDL crash if we pass back the cloned buffer?
+ mCamera->doneWithFrame_1_1(bufDesc);
+ break;
+ case eNoAutoReturn:
+ // Hang onto the buffer handle for now -- the client will return it explicitly later
+ mHeldBuffers.push(bufDesc);
+ }
+
+
+ ALOGD("Frame handling complete");
+ }
+
+
+ // Update our received frame count and notify anybody who cares that things have changed
+ mLock.lock();
+ if (timeToStop) {
+ mRunning = false;
+ } else {
+ mFramesReceived++;
+ }
+ mLock.unlock();
+ mSignal.notify_all();
+
+ return Void();
+}
+
+
+bool FrameHandler::copyBufferContents(const BufferDesc_1_0& tgtBuffer,
+ const BufferDesc_1_1& srcBuffer) {
+ bool success = true;
+ const AHardwareBuffer_Desc* pSrcDesc =
+ reinterpret_cast<const AHardwareBuffer_Desc *>(&srcBuffer.buffer.description);
+
+ // Make sure we don't run off the end of either buffer
+ const unsigned width = std::min(tgtBuffer.width,
+ pSrcDesc->width);
+ const unsigned height = std::min(tgtBuffer.height,
+ pSrcDesc->height);
+
+ sp<android::GraphicBuffer> tgt = new android::GraphicBuffer(tgtBuffer.memHandle,
+ android::GraphicBuffer::CLONE_HANDLE,
+ tgtBuffer.width,
+ tgtBuffer.height,
+ tgtBuffer.format,
+ 1,
+ tgtBuffer.usage,
+ tgtBuffer.stride);
+ sp<android::GraphicBuffer> src = new android::GraphicBuffer(srcBuffer.buffer.nativeHandle,
+ android::GraphicBuffer::CLONE_HANDLE,
+ pSrcDesc->width,
+ pSrcDesc->height,
+ pSrcDesc->format,
+ pSrcDesc->layers,
+ pSrcDesc->usage,
+ pSrcDesc->stride);
+
+ // Lock our source buffer for reading (current expectation are for this to be NV21 format)
+ uint8_t* srcPixels = nullptr;
+ src->lock(GRALLOC_USAGE_SW_READ_OFTEN, (void**)&srcPixels);
+
+ // Lock our target buffer for writing (should be either RGBA8888 or BGRA8888 format)
+ uint32_t* tgtPixels = nullptr;
+ tgt->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)&tgtPixels);
+
+ if (srcPixels && tgtPixels) {
+ using namespace ::android::hardware::automotive::evs::common;
+ if (tgtBuffer.format == HAL_PIXEL_FORMAT_RGBA_8888) {
+ if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21
+ Utils::copyNV21toRGB32(width, height,
+ srcPixels,
+ tgtPixels, tgtBuffer.stride);
+ } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12
+ Utils::copyYV12toRGB32(width, height,
+ srcPixels,
+ tgtPixels, tgtBuffer.stride);
+ } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV
+ Utils::copyYUYVtoRGB32(width, height,
+ srcPixels, pSrcDesc->stride,
+ tgtPixels, tgtBuffer.stride);
+ } else if (pSrcDesc->format == tgtBuffer.format) { // 32bit RGBA
+ Utils::copyMatchedInterleavedFormats(width, height,
+ srcPixels, pSrcDesc->stride,
+ tgtPixels, tgtBuffer.stride,
+ tgtBuffer.pixelSize);
+ } else {
+ ALOGE("Camera buffer format is not supported");
+ success = false;
+ }
+ } else if (tgtBuffer.format == HAL_PIXEL_FORMAT_BGRA_8888) {
+ if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21
+ Utils::copyNV21toBGR32(width, height,
+ srcPixels,
+ tgtPixels, tgtBuffer.stride);
+ } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12
+ Utils::copyYV12toBGR32(width, height,
+ srcPixels,
+ tgtPixels, tgtBuffer.stride);
+ } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV
+ Utils::copyYUYVtoBGR32(width, height,
+ srcPixels, pSrcDesc->stride,
+ tgtPixels, tgtBuffer.stride);
+ } else if (pSrcDesc->format == tgtBuffer.format) { // 32bit RGBA
+ Utils::copyMatchedInterleavedFormats(width, height,
+ srcPixels, pSrcDesc->stride,
+ tgtPixels, tgtBuffer.stride,
+ tgtBuffer.pixelSize);
+ } else {
+ ALOGE("Camera buffer format is not supported");
+ success = false;
+ }
+ } else {
+ // We always expect 32 bit RGB for the display output for now. Is there a need for 565?
+ ALOGE("Diplay buffer is always expected to be 32bit RGBA");
+ success = false;
+ }
+ } else {
+ ALOGE("Failed to lock buffer contents for contents transfer");
+ success = false;
+ }
+
+ if (srcPixels) {
+ src->unlock();
+ }
+ if (tgtPixels) {
+ tgt->unlock();
+ }
+
+ return success;
+}
+
+void FrameHandler::getFrameDimension(unsigned* width, unsigned* height) {
+ if (width) {
+ *width = mFrameWidth;
+ }
+
+ if (height) {
+ *height = mFrameHeight;
+ }
+}
diff --git a/automotive/evs/1.1/vts/functional/FrameHandler.h b/automotive/evs/1.1/vts/functional/FrameHandler.h
new file mode 100644
index 0000000..49fa736
--- /dev/null
+++ b/automotive/evs/1.1/vts/functional/FrameHandler.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#ifndef EVS_VTS_FRAMEHANDLER_H
+#define EVS_VTS_FRAMEHANDLER_H
+
+#include <queue>
+
+#include <FrameHandler.h>
+
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_handle;
+using ::android::sp;
+using ::android::hardware::automotive::evs::V1_0::IEvsDisplay;
+using ::android::hardware::automotive::evs::V1_0::EvsResult;
+using ::android::hardware::automotive::evs::V1_0::CameraDesc;
+using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc;
+using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc;
+
+
+/*
+ * FrameHandler:
+ * This class can be used to receive camera imagery from an IEvsCamera implementation. Given an
+ * IEvsDisplay instance at startup, it will forward the received imagery to the display,
+ * providing a trivial implementation of a rear vew camera type application.
+ * Note that the video frames are delivered on a background thread, while the control interface
+ * is actuated from the applications foreground thread.
+ */
+class FrameHandler : public IEvsCameraStream {
+public:
+ enum BufferControlFlag {
+ eAutoReturn,
+ eNoAutoReturn,
+ };
+
+ FrameHandler(android::sp <IEvsCamera> pCamera, CameraDesc cameraInfo,
+ android::sp <IEvsDisplay> pDisplay = nullptr,
+ BufferControlFlag mode = eAutoReturn);
+ void shutdown();
+
+ bool startStream();
+ void asyncStopStream();
+ void blockingStopStream();
+
+ bool returnHeldBuffer();
+
+ bool isRunning();
+
+ void waitForFrameCount(unsigned frameCount);
+ void getFramesCounters(unsigned* received, unsigned* displayed);
+ void getFrameDimension(unsigned* width, unsigned* height);
+
+private:
+ // Implementation for ::android::hardware::automotive::evs::V1_1::IEvsCameraStream
+ Return<void> deliverFrame(const BufferDesc_1_0& buffer) override;
+ Return<void> notifyEvent(const EvsEvent& event) override;
+
+ // Local implementation details
+ bool copyBufferContents(const BufferDesc_1_0& tgtBuffer, const BufferDesc_1_1& srcBuffer);
+
+ // Values initialized as startup
+ android::sp <IEvsCamera> mCamera;
+ CameraDesc mCameraInfo;
+ android::sp <IEvsDisplay> mDisplay;
+ BufferControlFlag mReturnMode;
+
+ // Since we get frames delivered to us asynchronously via the IEvsCameraStream interface,
+ // we need to protect all member variables that may be modified while we're streaming
+ // (ie: those below)
+ std::mutex mLock;
+ std::condition_variable mSignal;
+
+ std::queue<BufferDesc_1_1> mHeldBuffers;
+ bool mRunning = false;
+ unsigned mFramesReceived = 0; // Simple counter -- rolls over eventually!
+ unsigned mFramesDisplayed = 0; // Simple counter -- rolls over eventually!
+ unsigned mFrameWidth = 0;
+ unsigned mFrameHeight = 0;
+};
+
+
+#endif //EVS_VTS_FRAMEHANDLER_H
diff --git a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp
new file mode 100644
index 0000000..4f7082a
--- /dev/null
+++ b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp
@@ -0,0 +1,526 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VtsHalEvsTest"
+
+
+// Note: We have't got a great way to indicate which target
+// should be tested, so we'll leave the interface served by the
+// default (mock) EVS driver here for easy reference. All
+// actual EVS drivers should serve on the EvsEnumeratorHw name,
+// however, so the code is checked in that way.
+//const static char kEnumeratorName[] = "EvsEnumeratorHw-Mock";
+const static char kEnumeratorName[] = "EvsEnumeratorHw";
+
+
+// These values are called out in the EVS design doc (as of Mar 8, 2017)
+static const int kMaxStreamStartMilliseconds = 500;
+static const int kMinimumFramesPerSecond = 10;
+
+static const int kSecondsToMilliseconds = 1000;
+static const int kMillisecondsToMicroseconds = 1000;
+static const float kNanoToMilliseconds = 0.000001f;
+static const float kNanoToSeconds = 0.000000001f;
+
+
+#include "FrameHandler.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <hidl/HidlTransportSupport.h>
+#include <hwbinder/ProcessState.h>
+#include <log/log.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+
+#include <android/log.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h>
+#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
+
+#include <VtsHalHidlTargetTestBase.h>
+#include <VtsHalHidlTargetTestEnvBase.h>
+
+using namespace ::android::hardware::automotive::evs::V1_1;
+
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::hidl_string;
+using ::android::sp;
+using ::android::hardware::automotive::evs::V1_0::CameraDesc;
+using ::android::hardware::automotive::evs::V1_0::DisplayDesc;
+using ::android::hardware::automotive::evs::V1_0::DisplayState;
+using ::android::hardware::automotive::evs::V1_0::IEvsEnumerator;
+using IEvsCamera_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCamera;
+using IEvsCamera_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCamera;
+
+// Test environment for Evs HIDL HAL.
+class EvsHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase {
+ public:
+ // get the test environment singleton
+ static EvsHidlEnvironment* Instance() {
+ static EvsHidlEnvironment* instance = new EvsHidlEnvironment;
+ return instance;
+ }
+
+ virtual void registerTestServices() override { registerTestService<IEvsEnumerator>(); }
+
+ private:
+ EvsHidlEnvironment() {}
+};
+
+// The main test class for EVS
+class EvsHidlTest : public ::testing::VtsHalHidlTargetTestBase {
+public:
+ virtual void SetUp() override {
+ // Make sure we can connect to the enumerator
+ string service_name =
+ EvsHidlEnvironment::Instance()->getServiceName<IEvsEnumerator>(kEnumeratorName);
+ pEnumerator = getService<IEvsEnumerator>(service_name);
+ ASSERT_NE(pEnumerator.get(), nullptr);
+
+ mIsHwModule = !service_name.compare(kEnumeratorName);
+ }
+
+ virtual void TearDown() override {}
+
+protected:
+ void loadCameraList() {
+ // SetUp() must run first!
+ assert(pEnumerator != nullptr);
+
+ // Get the camera list
+ pEnumerator->getCameraList([this](hidl_vec <CameraDesc> cameraList) {
+ ALOGI("Camera list callback received %zu cameras",
+ cameraList.size());
+ cameraInfo.reserve(cameraList.size());
+ for (auto&& cam: cameraList) {
+ ALOGI("Found camera %s", cam.cameraId.c_str());
+ cameraInfo.push_back(cam);
+ }
+ }
+ );
+
+ // We insist on at least one camera for EVS to pass any camera tests
+ ASSERT_GE(cameraInfo.size(), 1u);
+ }
+
+ sp<IEvsEnumerator> pEnumerator; // Every test needs access to the service
+ std::vector <CameraDesc> cameraInfo; // Empty unless/until loadCameraList() is called
+ bool mIsHwModule; // boolean to tell current module under testing
+ // is HW module implementation.
+};
+
+
+// Test cases, their implementations, and corresponding requirements are
+// documented at go/aae-evs-public-api-test.
+
+/*
+ * CameraOpenClean:
+ * Opens each camera reported by the enumerator and then explicitly closes it via a
+ * call to closeCamera. Then repeats the test to ensure all cameras can be reopened.
+ */
+TEST_F(EvsHidlTest, CameraOpenClean) {
+ ALOGI("Starting CameraOpenClean test");
+
+ // Get the camera list
+ loadCameraList();
+
+ // Open and close each camera twice
+ for (auto&& cam: cameraInfo) {
+ for (int pass = 0; pass < 2; pass++) {
+ sp<IEvsCamera_1_1> pCam =
+ IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+ .withDefault(nullptr);
+ ASSERT_NE(pCam, nullptr);
+
+ // Verify that this camera self-identifies correctly
+ pCam->getCameraInfo([&cam](CameraDesc desc) {
+ ALOGD("Found camera %s", desc.cameraId.c_str());
+ EXPECT_EQ(cam.cameraId, desc.cameraId);
+ }
+ );
+
+ // Explicitly close the camera so resources are released right away
+ pEnumerator->closeCamera(pCam);
+ }
+ }
+}
+
+
+/*
+ * CameraOpenAggressive:
+ * Opens each camera reported by the enumerator twice in a row without an intervening closeCamera
+ * call. This ensures that the intended "aggressive open" behavior works. This is necessary for
+ * the system to be tolerant of shutdown/restart race conditions.
+ */
+TEST_F(EvsHidlTest, CameraOpenAggressive) {
+ ALOGI("Starting CameraOpenAggressive test");
+
+ // Get the camera list
+ loadCameraList();
+
+ // Open and close each camera twice
+ for (auto&& cam: cameraInfo) {
+ sp<IEvsCamera_1_1> pCam =
+ IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+ .withDefault(nullptr);
+ ASSERT_NE(pCam, nullptr);
+
+ // Verify that this camera self-identifies correctly
+ pCam->getCameraInfo([&cam](CameraDesc desc) {
+ ALOGD("Found camera %s", desc.cameraId.c_str());
+ EXPECT_EQ(cam.cameraId, desc.cameraId);
+ }
+ );
+
+ sp<IEvsCamera_1_1> pCam2 =
+ IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+ .withDefault(nullptr);
+ ASSERT_NE(pCam, pCam2);
+ ASSERT_NE(pCam2, nullptr);
+
+ Return<EvsResult> result = pCam->setMaxFramesInFlight(2);
+ if (mIsHwModule) {
+ // Verify that the old camera rejects calls via HW module.
+ EXPECT_EQ(EvsResult::OWNERSHIP_LOST, EvsResult(result));
+ } else {
+ // default implementation supports multiple clients.
+ EXPECT_EQ(EvsResult::OK, EvsResult(result));
+ }
+
+ // Close the superceded camera
+ pEnumerator->closeCamera(pCam);
+
+ // Verify that the second camera instance self-identifies correctly
+ pCam2->getCameraInfo([&cam](CameraDesc desc) {
+ ALOGD("Found camera %s", desc.cameraId.c_str());
+ EXPECT_EQ(cam.cameraId, desc.cameraId);
+ }
+ );
+
+ // Close the second camera instance
+ pEnumerator->closeCamera(pCam2);
+ }
+
+ // Sleep here to ensure the destructor cleanup has time to run so we don't break follow on tests
+ sleep(1); // I hate that this is an arbitrary time to wait. :( b/36122635
+}
+
+
+/*
+ * CameraStreamPerformance:
+ * Measure and qualify the stream start up time and streaming frame rate of each reported camera
+ */
+TEST_F(EvsHidlTest, CameraStreamPerformance) {
+ ALOGI("Starting CameraStreamPerformance test");
+
+ // Get the camera list
+ loadCameraList();
+
+ // Test each reported camera
+ for (auto&& cam: cameraInfo) {
+ sp<IEvsCamera_1_1> pCam =
+ IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+ .withDefault(nullptr);
+ ASSERT_NE(pCam, nullptr);
+
+ // Set up a frame receiver object which will fire up its own thread
+ sp<FrameHandler> frameHandler = new FrameHandler(pCam, cam,
+ nullptr,
+ FrameHandler::eAutoReturn);
+
+ // Start the camera's video stream
+ nsecs_t start = systemTime(SYSTEM_TIME_MONOTONIC);
+ bool startResult = frameHandler->startStream();
+ ASSERT_TRUE(startResult);
+
+ // Ensure the first frame arrived within the expected time
+ frameHandler->waitForFrameCount(1);
+ nsecs_t firstFrame = systemTime(SYSTEM_TIME_MONOTONIC);
+ nsecs_t timeToFirstFrame = systemTime(SYSTEM_TIME_MONOTONIC) - start;
+ EXPECT_LE(nanoseconds_to_milliseconds(timeToFirstFrame), kMaxStreamStartMilliseconds);
+ printf("Measured time to first frame %0.2f ms\n", timeToFirstFrame * kNanoToMilliseconds);
+ ALOGI("Measured time to first frame %0.2f ms", timeToFirstFrame * kNanoToMilliseconds);
+
+ // Check aspect ratio
+ unsigned width = 0, height = 0;
+ frameHandler->getFrameDimension(&width, &height);
+ EXPECT_GE(width, height);
+
+ // Wait a bit, then ensure we get at least the required minimum number of frames
+ sleep(5);
+ nsecs_t end = systemTime(SYSTEM_TIME_MONOTONIC);
+ unsigned framesReceived = 0;
+ frameHandler->getFramesCounters(&framesReceived, nullptr);
+ framesReceived = framesReceived - 1; // Back out the first frame we already waited for
+ nsecs_t runTime = end - firstFrame;
+ float framesPerSecond = framesReceived / (runTime * kNanoToSeconds);
+ printf("Measured camera rate %3.2f fps\n", framesPerSecond);
+ ALOGI("Measured camera rate %3.2f fps", framesPerSecond);
+ EXPECT_GE(framesPerSecond, kMinimumFramesPerSecond);
+
+ // Even when the camera pointer goes out of scope, the FrameHandler object will
+ // keep the stream alive unless we tell it to shutdown.
+ // Also note that the FrameHandle and the Camera have a mutual circular reference, so
+ // we have to break that cycle in order for either of them to get cleaned up.
+ frameHandler->shutdown();
+
+ // Explicitly release the camera
+ pEnumerator->closeCamera(pCam);
+ }
+}
+
+
+/*
+ * CameraStreamBuffering:
+ * Ensure the camera implementation behaves properly when the client holds onto buffers for more
+ * than one frame time. The camera must cleanly skip frames until the client is ready again.
+ */
+TEST_F(EvsHidlTest, CameraStreamBuffering) {
+ ALOGI("Starting CameraStreamBuffering test");
+
+ // Arbitrary constant (should be > 1 and less than crazy)
+ static const unsigned int kBuffersToHold = 6;
+
+ // Get the camera list
+ loadCameraList();
+
+ // Test each reported camera
+ for (auto&& cam: cameraInfo) {
+
+ sp<IEvsCamera_1_1> pCam =
+ IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+ .withDefault(nullptr);
+ ASSERT_NE(pCam, nullptr);
+
+ // Ask for a crazy number of buffers in flight to ensure it errors correctly
+ Return<EvsResult> badResult = pCam->setMaxFramesInFlight(0xFFFFFFFF);
+ EXPECT_EQ(EvsResult::BUFFER_NOT_AVAILABLE, badResult);
+
+ // Now ask for exactly two buffers in flight as we'll test behavior in that case
+ Return<EvsResult> goodResult = pCam->setMaxFramesInFlight(kBuffersToHold);
+ EXPECT_EQ(EvsResult::OK, goodResult);
+
+
+ // Set up a frame receiver object which will fire up its own thread.
+ sp<FrameHandler> frameHandler = new FrameHandler(pCam, cam,
+ nullptr,
+ FrameHandler::eNoAutoReturn);
+
+ // Start the camera's video stream
+ bool startResult = frameHandler->startStream();
+ ASSERT_TRUE(startResult);
+
+ // Check that the video stream stalls once we've gotten exactly the number of buffers
+ // we requested since we told the frameHandler not to return them.
+ sleep(2); // 1 second should be enough for at least 5 frames to be delivered worst case
+ unsigned framesReceived = 0;
+ frameHandler->getFramesCounters(&framesReceived, nullptr);
+ ASSERT_EQ(kBuffersToHold, framesReceived) << "Stream didn't stall at expected buffer limit";
+
+
+ // Give back one buffer
+ bool didReturnBuffer = frameHandler->returnHeldBuffer();
+ EXPECT_TRUE(didReturnBuffer);
+
+ // Once we return a buffer, it shouldn't take more than 1/10 second to get a new one
+ // filled since we require 10fps minimum -- but give a 10% allowance just in case.
+ usleep(110 * kMillisecondsToMicroseconds);
+ frameHandler->getFramesCounters(&framesReceived, nullptr);
+ EXPECT_EQ(kBuffersToHold+1, framesReceived) << "Stream should've resumed";
+
+ // Even when the camera pointer goes out of scope, the FrameHandler object will
+ // keep the stream alive unless we tell it to shutdown.
+ // Also note that the FrameHandle and the Camera have a mutual circular reference, so
+ // we have to break that cycle in order for either of them to get cleaned up.
+ frameHandler->shutdown();
+
+ // Explicitly release the camera
+ pEnumerator->closeCamera(pCam);
+ }
+}
+
+
+/*
+ * CameraToDisplayRoundTrip:
+ * End to end test of data flowing from the camera to the display. Each delivered frame of camera
+ * imagery is simply copied to the display buffer and presented on screen. This is the one test
+ * which a human could observe to see the operation of the system on the physical display.
+ */
+TEST_F(EvsHidlTest, CameraToDisplayRoundTrip) {
+ ALOGI("Starting CameraToDisplayRoundTrip test");
+
+ // Get the camera list
+ loadCameraList();
+
+ // Request exclusive access to the EVS display
+ sp<IEvsDisplay> pDisplay = pEnumerator->openDisplay();
+ ASSERT_NE(pDisplay, nullptr);
+
+ // Test each reported camera
+ for (auto&& cam: cameraInfo) {
+ sp<IEvsCamera_1_1> pCam =
+ IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+ .withDefault(nullptr);
+ ASSERT_NE(pCam, nullptr);
+
+ // Set up a frame receiver object which will fire up its own thread.
+ sp<FrameHandler> frameHandler = new FrameHandler(pCam, cam,
+ pDisplay,
+ FrameHandler::eAutoReturn);
+
+
+ // Activate the display
+ pDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME);
+
+ // Start the camera's video stream
+ bool startResult = frameHandler->startStream();
+ ASSERT_TRUE(startResult);
+
+ // Wait a while to let the data flow
+ static const int kSecondsToWait = 5;
+ const int streamTimeMs = kSecondsToWait * kSecondsToMilliseconds -
+ kMaxStreamStartMilliseconds;
+ const unsigned minimumFramesExpected = streamTimeMs * kMinimumFramesPerSecond /
+ kSecondsToMilliseconds;
+ sleep(kSecondsToWait);
+ unsigned framesReceived = 0;
+ unsigned framesDisplayed = 0;
+ frameHandler->getFramesCounters(&framesReceived, &framesDisplayed);
+ EXPECT_EQ(framesReceived, framesDisplayed);
+ EXPECT_GE(framesDisplayed, minimumFramesExpected);
+
+ // Turn off the display (yes, before the stream stops -- it should be handled)
+ pDisplay->setDisplayState(DisplayState::NOT_VISIBLE);
+
+ // Shut down the streamer
+ frameHandler->shutdown();
+
+ // Explicitly release the camera
+ pEnumerator->closeCamera(pCam);
+ }
+
+ // Explicitly release the display
+ pEnumerator->closeDisplay(pDisplay);
+}
+
+
+/*
+ * MultiCameraStream:
+ * Verify that each client can start and stop video streams on the same
+ * underlying camera.
+ */
+TEST_F(EvsHidlTest, MultiCameraStream) {
+ ALOGI("Starting MultiCameraStream test");
+
+ if (mIsHwModule) {
+ // This test is not for HW module implementation.
+ return;
+ }
+
+ // Get the camera list
+ loadCameraList();
+
+ // Test each reported camera
+ for (auto&& cam: cameraInfo) {
+ // Create two camera clients.
+ sp<IEvsCamera_1_1> pCam0 =
+ IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+ .withDefault(nullptr);
+ ASSERT_NE(pCam0, nullptr);
+
+ sp<IEvsCamera_1_1> pCam1 =
+ IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+ .withDefault(nullptr);
+ ASSERT_NE(pCam1, nullptr);
+
+ // Set up per-client frame receiver objects which will fire up its own thread
+ sp<FrameHandler> frameHandler0 = new FrameHandler(pCam0, cam,
+ nullptr,
+ FrameHandler::eAutoReturn);
+ ASSERT_NE(frameHandler0, nullptr);
+
+ sp<FrameHandler> frameHandler1 = new FrameHandler(pCam1, cam,
+ nullptr,
+ FrameHandler::eAutoReturn);
+ ASSERT_NE(frameHandler1, nullptr);
+
+ // Start the camera's video stream via client 0
+ bool startResult = false;
+ startResult = frameHandler0->startStream() &&
+ frameHandler1->startStream();
+ ASSERT_TRUE(startResult);
+
+ // Ensure the stream starts
+ frameHandler0->waitForFrameCount(1);
+ frameHandler1->waitForFrameCount(1);
+
+ nsecs_t firstFrame = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ // Wait a bit, then ensure both clients get at least the required minimum number of frames
+ sleep(5);
+ nsecs_t end = systemTime(SYSTEM_TIME_MONOTONIC);
+ unsigned framesReceived0 = 0, framesReceived1 = 0;
+ frameHandler0->getFramesCounters(&framesReceived0, nullptr);
+ frameHandler1->getFramesCounters(&framesReceived1, nullptr);
+ framesReceived0 = framesReceived0 - 1; // Back out the first frame we already waited for
+ framesReceived1 = framesReceived1 - 1; // Back out the first frame we already waited for
+ nsecs_t runTime = end - firstFrame;
+ float framesPerSecond0 = framesReceived0 / (runTime * kNanoToSeconds);
+ float framesPerSecond1 = framesReceived1 / (runTime * kNanoToSeconds);
+ printf("Measured camera rate %3.2f fps and %3.2f fps\n", framesPerSecond0, framesPerSecond1);
+ ALOGI("Measured camera rate %3.2f fps and %3.2f fps", framesPerSecond0, framesPerSecond1);
+ EXPECT_GE(framesPerSecond0, kMinimumFramesPerSecond);
+ EXPECT_GE(framesPerSecond1, kMinimumFramesPerSecond);
+
+ // Shutdown one client
+ frameHandler0->shutdown();
+
+ // Read frame counters again
+ frameHandler0->getFramesCounters(&framesReceived0, nullptr);
+ frameHandler1->getFramesCounters(&framesReceived1, nullptr);
+
+ // Wait a bit again
+ sleep(5);
+ unsigned framesReceivedAfterStop0 = 0, framesReceivedAfterStop1 = 0;
+ frameHandler0->getFramesCounters(&framesReceivedAfterStop0, nullptr);
+ frameHandler1->getFramesCounters(&framesReceivedAfterStop1, nullptr);
+ EXPECT_EQ(framesReceived0, framesReceivedAfterStop0);
+ EXPECT_LT(framesReceived1, framesReceivedAfterStop1);
+
+ // Shutdown another
+ frameHandler1->shutdown();
+
+ // Explicitly release the camera
+ pEnumerator->closeCamera(pCam0);
+ pEnumerator->closeCamera(pCam1);
+ }
+}
+
+
+int main(int argc, char** argv) {
+ ::testing::AddGlobalTestEnvironment(EvsHidlEnvironment::Instance());
+ ::testing::InitGoogleTest(&argc, argv);
+ EvsHidlEnvironment::Instance()->init(&argc, argv);
+ int status = RUN_ALL_TESTS();
+ ALOGI("Test result = %d", status);
+ return status;
+}