| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "StreamHandler.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <log/log.h> |
| #include <cutils/native_handle.h> |
| |
| #include <ui/GraphicBufferAllocator.h> |
| #include <ui/GraphicBufferMapper.h> |
| |
| #include "Frame.h" |
| #include "ResourceManager.h" |
| |
| namespace android { |
| namespace automotive { |
| namespace evs { |
| namespace support { |
| |
| using ::std::lock_guard; |
| using ::std::unique_lock; |
| |
| StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera) : |
| mCamera(pCamera), |
| mAnalyzeCallback(nullptr), |
| mAnalyzerRunning(false) |
| { |
| // We rely on the camera having at least two buffers available since we'll hold one and |
| // expect the camera to be able to capture a new image in the background. |
| pCamera->setMaxFramesInFlight(2); |
| } |
| |
| // TODO(b/130246343): investigate further to make sure the resources are cleaned |
| // up properly in the shutdown logic. |
| void StreamHandler::shutdown() |
| { |
| // Tell the camera to stop streaming. |
| // This will result in a null frame being delivered when the stream actually stops. |
| mCamera->stopVideoStream(); |
| |
| // Wait until the stream has actually stopped |
| unique_lock<mutex> lock(mLock); |
| if (mRunning) { |
| mSignal.wait(lock, [this]() { return !mRunning; }); |
| } |
| |
| // 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; |
| } |
| |
| |
| bool StreamHandler::startStream() { |
| lock_guard<mutex> lock(mLock); |
| |
| if (!mRunning) { |
| // Tell the camera to start streaming |
| Return <EvsResult> result = mCamera->startVideoStream(this); |
| if (result != EvsResult::OK) { |
| return false; |
| } |
| |
| // Mark ourselves as running |
| mRunning = true; |
| } |
| |
| return true; |
| } |
| |
| |
| bool StreamHandler::newDisplayFrameAvailable() { |
| lock_guard<mutex> lock(mLock); |
| return (mReadyBuffer >= 0); |
| } |
| |
| |
| const BufferDesc& StreamHandler::getNewDisplayFrame() { |
| lock_guard<mutex> lock(mLock); |
| |
| if (mHeldBuffer >= 0) { |
| ALOGE("Ignored call for new frame while still holding the old one."); |
| } else { |
| if (mReadyBuffer < 0) { |
| ALOGE("Returning invalid buffer because we don't have any. " |
| " Call newDisplayFrameAvailable first?"); |
| mReadyBuffer = 0; // This is a lie! |
| } |
| |
| // Move the ready buffer into the held position, and clear the ready position |
| mHeldBuffer = mReadyBuffer; |
| mReadyBuffer = -1; |
| } |
| |
| if (mRenderCallback == nullptr) { |
| return mOriginalBuffers[mHeldBuffer]; |
| } else { |
| return mProcessedBuffers[mHeldBuffer]; |
| } |
| } |
| |
| |
| void StreamHandler::doneWithFrame(const BufferDesc& buffer) { |
| lock_guard<mutex> lock(mLock); |
| |
| // We better be getting back the buffer we original delivered! |
| if ((mHeldBuffer < 0) |
| || (buffer.bufferId != mOriginalBuffers[mHeldBuffer].bufferId)) { |
| ALOGE("StreamHandler::doneWithFrame got an unexpected buffer!"); |
| ALOGD("Held buffer id: %d, input buffer id: %d", |
| mOriginalBuffers[mHeldBuffer].bufferId, buffer.bufferId); |
| return; |
| } |
| |
| // Send the buffer back to the underlying camera |
| mCamera->doneWithFrame(mOriginalBuffers[mHeldBuffer]); |
| |
| // Clear the held position |
| mHeldBuffer = -1; |
| } |
| |
| |
| Return<void> StreamHandler::deliverFrame(const BufferDesc& buffer) { |
| ALOGD("Received a frame from the camera. NativeHandle:%p, buffer id:%d", |
| buffer.memHandle.getNativeHandle(), buffer.bufferId); |
| |
| // Take the lock to protect our frame slots and running state variable |
| { |
| lock_guard <mutex> lock(mLock); |
| |
| if (buffer.memHandle.getNativeHandle() == nullptr) { |
| // Signal that the last frame has been received and the stream is stopped |
| mRunning = false; |
| } else { |
| // Do we already have a "ready" frame? |
| if (mReadyBuffer >= 0) { |
| // Send the previously saved buffer back to the camera unused |
| mCamera->doneWithFrame(mOriginalBuffers[mReadyBuffer]); |
| |
| // We'll reuse the same ready buffer index |
| } else if (mHeldBuffer >= 0) { |
| // The client is holding a buffer, so use the other slot for "on deck" |
| mReadyBuffer = 1 - mHeldBuffer; |
| } else { |
| // This is our first buffer, so just pick a slot |
| mReadyBuffer = 0; |
| } |
| |
| // Save this frame until our client is interested in it |
| mOriginalBuffers[mReadyBuffer] = buffer; |
| |
| // If render callback is not null, process the frame with render |
| // callback. |
| if (mRenderCallback != nullptr) { |
| processFrame(mOriginalBuffers[mReadyBuffer], |
| mProcessedBuffers[mReadyBuffer]); |
| } else { |
| ALOGI("Render callback is null in deliverFrame."); |
| } |
| |
| // If analyze callback is not null and the analyze thread is |
| // available, copy the frame and run the analyze callback in |
| // analyze thread. |
| { |
| std::shared_lock<std::shared_mutex> analyzerLock(mAnalyzerLock); |
| if (mAnalyzeCallback != nullptr && !mAnalyzerRunning) { |
| copyAndAnalyzeFrame(mOriginalBuffers[mReadyBuffer]); |
| } |
| } |
| } |
| } |
| |
| // Notify anybody who cares that things have changed |
| mSignal.notify_all(); |
| |
| return Void(); |
| } |
| |
| void StreamHandler::attachRenderCallback(BaseRenderCallback* callback) { |
| ALOGD("StreamHandler::attachRenderCallback"); |
| |
| lock_guard<mutex> lock(mLock); |
| |
| if (mRenderCallback != nullptr) { |
| ALOGW("Ignored! There should only be one render callback"); |
| return; |
| } |
| mRenderCallback = callback; |
| } |
| |
| void StreamHandler::detachRenderCallback() { |
| ALOGD("StreamHandler::detachRenderCallback"); |
| |
| lock_guard<mutex> lock(mLock); |
| |
| mRenderCallback = nullptr; |
| } |
| |
| void StreamHandler::attachAnalyzeCallback(BaseAnalyzeCallback* callback) { |
| ALOGD("StreamHandler::attachAnalyzeCallback"); |
| |
| if (mAnalyzeCallback != nullptr) { |
| ALOGW("Ignored! There should only be one analyze callcack"); |
| return; |
| } |
| |
| { |
| lock_guard<std::shared_mutex> lock(mAnalyzerLock); |
| mAnalyzeCallback = callback; |
| } |
| } |
| |
| void StreamHandler::detachAnalyzeCallback() { |
| ALOGD("StreamHandler::detachAnalyzeCallback"); |
| |
| { |
| std::unique_lock<std::shared_mutex> lock(mAnalyzerLock); |
| |
| // Wait until current running analyzer ends |
| mAnalyzerSignal.wait(lock, [this] { return !mAnalyzerRunning; }); |
| mAnalyzeCallback = nullptr; |
| } |
| } |
| |
| bool isSameFormat(const BufferDesc& input, const BufferDesc& output) { |
| return input.width == output.width |
| && input.height == output.height |
| && input.format == output.format |
| && input.usage == output.usage |
| && input.stride == output.stride |
| && input.pixelSize == output.pixelSize; |
| } |
| |
| bool allocate(BufferDesc& buffer) { |
| ALOGD("StreamHandler::allocate"); |
| buffer_handle_t handle; |
| android::GraphicBufferAllocator& alloc(android::GraphicBufferAllocator::get()); |
| android::status_t result = alloc.allocate( |
| buffer.width, buffer.height, buffer.format, 1, buffer.usage, |
| &handle, &buffer.stride, 0, "EvsDisplay"); |
| if (result != android::NO_ERROR) { |
| ALOGE("Error %d allocating %d x %d graphics buffer", result, buffer.width, |
| buffer.height); |
| return false; |
| } |
| |
| // The reason that we have to check null for "handle" is because that the |
| // above "result" might not cover all the failure scenarios. |
| // By looking into Gralloc4.cpp (and 3, 2, as well), it turned out that if |
| // there is anything that goes wrong in the process of buffer importing (see |
| // Ln 385 in Gralloc4.cpp), the error won't be covered by the above "result" |
| // we got from "allocate" method. In other words, it means that there is |
| // still a chance that the "result" is "NO_ERROR" but the handle is nullptr |
| // (that means buffer importing failed). |
| if (!handle) { |
| ALOGE("We didn't get a buffer handle back from the allocator"); |
| return false; |
| } |
| |
| buffer.memHandle = hidl_handle(handle); |
| return true; |
| } |
| |
| bool StreamHandler::processFrame(const BufferDesc& input, |
| BufferDesc& output) { |
| ALOGD("StreamHandler::processFrame"); |
| if (!isSameFormat(input, output) |
| || output.memHandle.getNativeHandle() == nullptr) { |
| output.width = input.width; |
| output.height = input.height; |
| output.format = input.format; |
| output.usage = input.usage; |
| output.stride = input.stride; |
| output.pixelSize = input.pixelSize; |
| |
| // free the allocated output frame handle if it is not null |
| if (output.memHandle.getNativeHandle() != nullptr) { |
| GraphicBufferAllocator::get().free(output.memHandle); |
| } |
| |
| if (!allocate(output)) { |
| ALOGE("Error allocating buffer"); |
| return false; |
| } |
| } |
| output.bufferId = input.bufferId; |
| |
| // Create a GraphicBuffer from the existing handle |
| sp<GraphicBuffer> inputBuffer = new GraphicBuffer( |
| input.memHandle, GraphicBuffer::CLONE_HANDLE, input.width, |
| input.height, input.format, 1, // layer count |
| GRALLOC_USAGE_HW_TEXTURE, input.stride); |
| |
| if (inputBuffer.get() == nullptr) { |
| ALOGE("Failed to allocate GraphicBuffer to wrap image handle"); |
| // Returning "true" in this error condition because we already released |
| // the previous image (if any) and so the texture may change in |
| // unpredictable ways now! |
| return false; |
| } |
| |
| // Lock the input GraphicBuffer and map it to a pointer. If we failed to |
| // lock, return false. |
| void* inputDataPtr; |
| inputBuffer->lock( |
| GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER, |
| &inputDataPtr); |
| |
| // Unlock the buffer and return if lock did not succeed. |
| if (!inputDataPtr) { |
| ALOGE("Failed to gain read access to image buffer"); |
| |
| // The program reaches at here when it fails to lock the buffer. But |
| // it is still safer to unlock it. The reason is as described in "lock" |
| // method in Gralloc.h: "The ownership of acquireFence is always |
| // transferred to the callee, even on errors." |
| // And even if the buffer was not locked, it does not harm anything |
| // given the comment for "unlock" method in IMapper.hal: |
| // "`BAD_BUFFER` if the buffer is invalid or not locked." |
| inputBuffer->unlock(); |
| return false; |
| } |
| |
| // Lock the allocated buffer in output BufferDesc and map it to a pointer |
| void* outputDataPtr = nullptr; |
| android::GraphicBufferMapper& mapper = android::GraphicBufferMapper::get(); |
| mapper.lock(output.memHandle, |
| GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER, |
| android::Rect(output.width, output.height), |
| (void**)&outputDataPtr); |
| |
| // If we failed to lock the pixel buffer, return false, and unlock both |
| // input and output buffers. |
| if (!outputDataPtr) { |
| ALOGE("Failed to gain write access to image buffer"); |
| |
| // Please refer to the previous "if" block for why we want to unlock |
| // the buffers even if the buffer locking fails. |
| inputBuffer->unlock(); |
| mapper.unlock(output.memHandle); |
| return false; |
| } |
| |
| // Wrap the raw data and copied data, and pass them to the callback. |
| Frame inputFrame = { |
| .width = input.width, |
| .height = input.height, |
| .stride = input.stride, |
| .data = (uint8_t*)inputDataPtr |
| }; |
| |
| Frame outputFrame = { |
| .width = output.width, |
| .height = output.height, |
| .stride = output.stride, |
| .data = (uint8_t*)outputDataPtr |
| }; |
| |
| mRenderCallback->render(inputFrame, outputFrame); |
| |
| // Unlock the buffers after all changes to the buffer are completed. |
| inputBuffer->unlock(); |
| mapper.unlock(output.memHandle); |
| |
| return true; |
| } |
| |
| bool StreamHandler::copyAndAnalyzeFrame(const BufferDesc& input) { |
| ALOGD("StreamHandler::copyAndAnalyzeFrame"); |
| |
| // TODO(b/130246434): make the following into a method. Some lines are |
| // duplicated with processFrame, move them into new methods as well. |
| if (!isSameFormat(input, mAnalyzeBuffer) |
| || mAnalyzeBuffer.memHandle.getNativeHandle() == nullptr) { |
| mAnalyzeBuffer.width = input.width; |
| mAnalyzeBuffer.height = input.height; |
| mAnalyzeBuffer.format = input.format; |
| mAnalyzeBuffer.usage = input.usage; |
| mAnalyzeBuffer.stride = input.stride; |
| mAnalyzeBuffer.pixelSize = input.pixelSize; |
| mAnalyzeBuffer.bufferId = input.bufferId; |
| |
| // free the allocated output frame handle if it is not null |
| if (mAnalyzeBuffer.memHandle.getNativeHandle() != nullptr) { |
| GraphicBufferAllocator::get().free(mAnalyzeBuffer.memHandle); |
| } |
| |
| if (!allocate(mAnalyzeBuffer)) { |
| ALOGE("Error allocating buffer"); |
| return false; |
| } |
| } |
| |
| // create a GraphicBuffer from the existing handle |
| sp<GraphicBuffer> inputBuffer = new GraphicBuffer( |
| input.memHandle, GraphicBuffer::CLONE_HANDLE, input.width, |
| input.height, input.format, 1, // layer count |
| GRALLOC_USAGE_HW_TEXTURE, input.stride); |
| |
| if (inputBuffer.get() == nullptr) { |
| ALOGE("Failed to allocate GraphicBuffer to wrap image handle"); |
| // Returning "true" in this error condition because we already released the |
| // previous image (if any) and so the texture may change in unpredictable |
| // ways now! |
| return false; |
| } |
| |
| // Lock the input GraphicBuffer and map it to a pointer. If we failed to |
| // lock, return false. |
| void* inputDataPtr; |
| inputBuffer->lock( |
| GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER, |
| &inputDataPtr); |
| if (!inputDataPtr) { |
| ALOGE("Failed to gain read access to imageGraphicBuffer"); |
| inputBuffer->unlock(); |
| return false; |
| } |
| |
| // Lock the allocated buffer in output BufferDesc and map it to a pointer |
| void* analyzeDataPtr = nullptr; |
| android::GraphicBufferMapper::get().lock( |
| mAnalyzeBuffer.memHandle, |
| GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER, |
| android::Rect(mAnalyzeBuffer.width, mAnalyzeBuffer.height), |
| (void**)&analyzeDataPtr); |
| |
| // If we failed to lock the pixel buffer, return false, and unlock both |
| // input and output buffers. |
| if (!analyzeDataPtr) { |
| ALOGE("Camera failed to gain access to image buffer for analyzing"); |
| return false; |
| } |
| |
| // Wrap the raw data and copied data, and pass them to the callback. |
| Frame analyzeFrame = { |
| .width = mAnalyzeBuffer.width, |
| .height = mAnalyzeBuffer.height, |
| .stride = mAnalyzeBuffer.stride, |
| .data = (uint8_t*)analyzeDataPtr, |
| }; |
| |
| memcpy(analyzeDataPtr, inputDataPtr, mAnalyzeBuffer.stride * mAnalyzeBuffer.height * 4); |
| |
| // Unlock the buffers after all changes to the buffer are completed. |
| inputBuffer->unlock(); |
| |
| mAnalyzerRunning = true; |
| std::thread([this, analyzeFrame]() { |
| ALOGD("StreamHandler: Analyze Thread starts"); |
| |
| std::shared_lock<std::shared_mutex> lock(mAnalyzerLock); |
| if (this->mAnalyzeCallback != nullptr) { |
| this->mAnalyzeCallback->analyze(analyzeFrame); |
| android::GraphicBufferMapper::get().unlock(this->mAnalyzeBuffer.memHandle); |
| } |
| this->mAnalyzerRunning = false; |
| mAnalyzerSignal.notify_one(); |
| ALOGD("StreamHandler: Analyze Thread ends"); |
| }).detach(); |
| |
| return true; |
| } |
| |
| } // namespace support |
| } // namespace evs |
| } // namespace automotive |
| } // namespace android |