blob: 8181e4733e98e79f594416d4004baedd96fea9d4 [file] [log] [blame]
/*
* Copyright (C) 2023 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 "EvsVideoEmulatedCamera.h"
#include <aidl/android/hardware/automotive/evs/EvsResult.h>
#include <aidlcommonsupport/NativeHandle.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <media/stagefright/MediaCodecConstants.h>
#include <ui/GraphicBufferAllocator.h>
#include <utils/SystemClock.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <tuple>
#include <utility>
namespace aidl::android::hardware::automotive::evs::implementation {
namespace {
struct FormatDeleter {
void operator()(AMediaFormat* format) const { AMediaFormat_delete(format); }
};
} // namespace
EvsVideoEmulatedCamera::EvsVideoEmulatedCamera(Sigil, const char* deviceName,
std::unique_ptr<ConfigManager::CameraInfo>& camInfo)
: mVideoFileName(deviceName), mCameraInfo(camInfo) {
mDescription.id = mVideoFileName;
/* set camera metadata */
if (camInfo) {
uint8_t* ptr = reinterpret_cast<uint8_t*>(camInfo->characteristics);
const size_t len = get_camera_metadata_size(camInfo->characteristics);
mDescription.metadata.insert(mDescription.metadata.end(), ptr, ptr + len);
}
initializeParameters();
}
bool EvsVideoEmulatedCamera::initialize() {
// Open file.
mVideoFd = open(mVideoFileName.c_str(), 0, O_RDONLY);
if (mVideoFd < 0) {
PLOG(ERROR) << __func__ << ": Failed to open video file \"" << mVideoFileName << "\".";
return false;
}
// Initialize Media Extractor.
{
mVideoExtractor.reset(AMediaExtractor_new());
off64_t filesize = lseek64(mVideoFd, 0, SEEK_END);
lseek(mVideoFd, 0, SEEK_SET);
const media_status_t status =
AMediaExtractor_setDataSourceFd(mVideoExtractor.get(), mVideoFd, 0, filesize);
if (status != AMEDIA_OK) {
LOG(ERROR) << __func__
<< ": Received error when initializing media extractor. Error code: "
<< status << ".";
return false;
}
}
// Initialize Media Codec and file format.
std::unique_ptr<AMediaFormat, FormatDeleter> format;
const char* mime;
bool selected = false;
int numTracks = AMediaExtractor_getTrackCount(mVideoExtractor.get());
for (int i = 0; i < numTracks; i++) {
format.reset(AMediaExtractor_getTrackFormat(mVideoExtractor.get(), i));
if (!AMediaFormat_getString(format.get(), AMEDIAFORMAT_KEY_MIME, &mime)) {
LOG(ERROR) << __func__ << ": Error in fetching format string";
continue;
}
if (!::android::base::StartsWith(mime, "video/")) {
continue;
}
const media_status_t status = AMediaExtractor_selectTrack(mVideoExtractor.get(), i);
if (status != AMEDIA_OK) {
LOG(ERROR) << __func__
<< ": Media extractor returned error to select track. Error Code: " << status
<< ".";
return false;
}
selected = true;
break;
}
if (!selected) {
LOG(ERROR) << __func__ << ": No video track in video file \"" << mVideoFileName << "\".";
return false;
}
mVideoCodec.reset(AMediaCodec_createDecoderByType(mime));
if (!mVideoCodec) {
LOG(ERROR) << __func__ << ": Unable to create decoder.";
return false;
}
mDescription.vendorFlags = 0xFFFFFFFF; // Arbitrary test value
mUsage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE |
GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY;
mFormat = HAL_PIXEL_FORMAT_YCBCR_420_888;
AMediaFormat_setInt32(format.get(), AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
{
const media_status_t status =
AMediaCodec_configure(mVideoCodec.get(), format.get(), nullptr, nullptr, 0);
if (status != AMEDIA_OK) {
LOG(ERROR) << __func__
<< ": Received error in configuring mCodec. Error code: " << status << ".";
return false;
}
}
format.reset(AMediaCodec_getOutputFormat(mVideoCodec.get()));
AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_WIDTH, &mWidth);
AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_HEIGHT, &mHeight);
return true;
}
void EvsVideoEmulatedCamera::generateFrames() {
while (true) {
{
std::lock_guard lock(mMutex);
if (mStreamState != StreamState::RUNNING) {
return;
}
}
renderOneFrame();
}
}
void EvsVideoEmulatedCamera::onCodecInputAvailable(const int32_t index) {
const size_t sampleSize = AMediaExtractor_getSampleSize(mVideoExtractor.get());
const int64_t presentationTime = AMediaExtractor_getSampleTime(mVideoExtractor.get());
size_t bufferSize = 0;
uint8_t* const codecInputBuffer =
AMediaCodec_getInputBuffer(mVideoCodec.get(), index, &bufferSize);
if (sampleSize > bufferSize) {
LOG(ERROR) << __func__ << ": Buffer is not large enough.";
}
if (presentationTime < 0) {
AMediaCodec_queueInputBuffer(mVideoCodec.get(), index, /* offset = */ 0,
/* size = */ 0, presentationTime,
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
LOG(INFO) << __func__ << ": Reaching the end of stream.";
return;
}
const size_t readSize =
AMediaExtractor_readSampleData(mVideoExtractor.get(), codecInputBuffer, sampleSize);
const media_status_t status = AMediaCodec_queueInputBuffer(
mVideoCodec.get(), index, /*offset = */ 0, readSize, presentationTime, /* flags = */ 0);
if (status != AMEDIA_OK) {
LOG(ERROR) << __func__
<< ": Received error in queueing input buffer. Error code: " << status;
}
}
void EvsVideoEmulatedCamera::onCodecOutputAvailable(const int32_t index,
const AMediaCodecBufferInfo& info) {
using std::chrono::duration_cast;
using std::chrono::microseconds;
using std::chrono::nanoseconds;
using AidlPixelFormat = ::aidl::android::hardware::graphics::common::PixelFormat;
using ::aidl::android::hardware::graphics::common::BufferUsage;
size_t decodedOutSize = 0;
uint8_t* const codecOutputBuffer =
AMediaCodec_getOutputBuffer(mVideoCodec.get(), index, &decodedOutSize) + info.offset;
std::size_t renderBufferId = static_cast<std::size_t>(-1);
buffer_handle_t renderBufferHandle = nullptr;
{
std::lock_guard lock(mMutex);
if (mStreamState != StreamState::RUNNING) {
return;
}
std::tie(renderBufferId, renderBufferHandle) = useBuffer_unsafe();
}
if (!renderBufferHandle) {
LOG(ERROR) << __func__ << ": Camera failed to get an available render buffer.";
return;
}
std::vector<BufferDesc> renderBufferDescs;
renderBufferDescs.push_back({
.buffer =
{
.description =
{
.width = static_cast<int32_t>(mWidth),
.height = static_cast<int32_t>(mHeight),
.layers = 1,
.format = static_cast<AidlPixelFormat>(mFormat),
.usage = static_cast<BufferUsage>(mUsage),
.stride = static_cast<int32_t>(mStride),
},
.handle = ::android::dupToAidl(renderBufferHandle),
},
.bufferId = static_cast<int32_t>(renderBufferId),
.deviceId = mDescription.id,
.timestamp = duration_cast<microseconds>(nanoseconds(::android::elapsedRealtimeNano()))
.count(),
});
// Lock our output buffer for writing
uint8_t* pixels = nullptr;
int32_t bytesPerStride = 0;
auto& mapper = ::android::GraphicBufferMapper::get();
mapper.lock(renderBufferHandle, GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER,
::android::Rect(mWidth, mHeight), (void**)&pixels, nullptr, &bytesPerStride);
// If we failed to lock the pixel buffer, we're about to crash, but log it first
if (!pixels) {
LOG(ERROR) << __func__ << ": Camera failed to gain access to image buffer for writing";
return;
}
std::size_t ySize = mHeight * mStride;
std::size_t uvSize = ySize / 4;
std::memcpy(pixels, codecOutputBuffer, ySize);
pixels += ySize;
uint8_t* u_head = codecOutputBuffer + ySize;
uint8_t* v_head = u_head + uvSize;
for (size_t i = 0; i < uvSize; ++i) {
*(pixels++) = *(u_head++);
*(pixels++) = *(v_head++);
}
const auto status =
AMediaCodec_releaseOutputBuffer(mVideoCodec.get(), index, /* render = */ false);
if (status != AMEDIA_OK) {
LOG(ERROR) << __func__
<< ": Received error in releasing output buffer. Error code: " << status;
}
// Release our output buffer
mapper.unlock(renderBufferHandle);
// Issue the (asynchronous) callback to the client -- can't be holding the lock
if (mStream && mStream->deliverFrame(renderBufferDescs).isOk()) {
LOG(DEBUG) << __func__ << ": Delivered " << renderBufferHandle
<< ", id = " << renderBufferId;
} else {
// This can happen if the client dies and is likely unrecoverable.
// To avoid consuming resources generating failing calls, we stop sending
// frames. Note, however, that the stream remains in the "STREAMING" state
// until cleaned up on the main thread.
LOG(ERROR) << __func__ << ": Frame delivery call failed in the transport layer.";
doneWithFrame(renderBufferDescs);
}
}
void EvsVideoEmulatedCamera::renderOneFrame() {
using std::chrono::duration_cast;
using std::chrono::microseconds;
using namespace std::chrono_literals;
// push to codec input
while (true) {
int codecInputBufferIdx =
AMediaCodec_dequeueInputBuffer(mVideoCodec.get(), /* timeoutUs = */ 0);
if (codecInputBufferIdx < 0) {
if (codecInputBufferIdx != AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
LOG(ERROR) << __func__
<< ": Received error in AMediaCodec_dequeueInputBuffer. Error code: "
<< codecInputBufferIdx;
}
break;
}
onCodecInputAvailable(codecInputBufferIdx);
AMediaExtractor_advance(mVideoExtractor.get());
}
// pop from codec output
AMediaCodecBufferInfo info;
int codecOutputputBufferIdx = AMediaCodec_dequeueOutputBuffer(
mVideoCodec.get(), &info, /* timeoutUs = */ duration_cast<microseconds>(1ms).count());
if (codecOutputputBufferIdx < 0) {
if (codecOutputputBufferIdx != AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
LOG(ERROR) << __func__
<< ": Received error in AMediaCodec_dequeueOutputBuffer. Error code: "
<< codecOutputputBufferIdx;
}
return;
}
onCodecOutputAvailable(codecOutputputBufferIdx, info);
}
void EvsVideoEmulatedCamera::initializeParameters() {
mParams.emplace(
CameraParam::BRIGHTNESS,
new CameraParameterDesc(/* min= */ 0, /* max= */ 255, /* step= */ 1, /* value= */ 255));
mParams.emplace(
CameraParam::CONTRAST,
new CameraParameterDesc(/* min= */ 0, /* max= */ 255, /* step= */ 1, /* value= */ 255));
mParams.emplace(
CameraParam::SHARPNESS,
new CameraParameterDesc(/* min= */ 0, /* max= */ 255, /* step= */ 1, /* value= */ 255));
}
::android::status_t EvsVideoEmulatedCamera::allocateOneFrame(buffer_handle_t* handle) {
static auto& alloc = ::android::GraphicBufferAllocator::get();
unsigned pixelsPerLine = 0;
const auto result = alloc.allocate(mWidth, mHeight, mFormat, 1, mUsage, handle, &pixelsPerLine,
0, "EvsVideoEmulatedCamera");
if (mStride == 0) {
// Gralloc defines stride in terms of pixels per line
mStride = pixelsPerLine;
} else if (mStride != pixelsPerLine) {
LOG(ERROR) << "We did not expect to get buffers with different strides!";
}
return result;
}
bool EvsVideoEmulatedCamera::startVideoStreamImpl_locked(
const std::shared_ptr<evs::IEvsCameraStream>& receiver, ndk::ScopedAStatus& /* status */,
std::unique_lock<std::mutex>& /* lck */) {
mStream = receiver;
const media_status_t status = AMediaCodec_start(mVideoCodec.get());
if (status != AMEDIA_OK) {
LOG(ERROR) << __func__ << ": Received error in starting decoder. Error code: " << status
<< ".";
return false;
}
mCaptureThread = std::thread([this]() { generateFrames(); });
return true;
}
bool EvsVideoEmulatedCamera::stopVideoStreamImpl_locked(ndk::ScopedAStatus& /* status */,
std::unique_lock<std::mutex>& lck) {
const media_status_t status = AMediaCodec_stop(mVideoCodec.get());
lck.unlock();
if (mCaptureThread.joinable()) {
mCaptureThread.join();
}
lck.lock();
return status == AMEDIA_OK;
}
bool EvsVideoEmulatedCamera::postVideoStreamStop_locked(ndk::ScopedAStatus& status,
std::unique_lock<std::mutex>& lck) {
if (!Base::postVideoStreamStop_locked(status, lck)) {
return false;
}
mStream = nullptr;
return true;
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::forcePrimaryClient(
const std::shared_ptr<evs::IEvsDisplay>& /* display */) {
/* Because EVS HW module reference implementation expects a single client at
* a time, this returns a success code always.
*/
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::getCameraInfo(evs::CameraDesc* _aidl_return) {
*_aidl_return = mDescription;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::getExtendedInfo(int32_t opaqueIdentifier,
std::vector<uint8_t>* value) {
const auto it = mExtInfo.find(opaqueIdentifier);
if (it == mExtInfo.end()) {
return ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int>(EvsResult::INVALID_ARG));
} else {
*value = mExtInfo[opaqueIdentifier];
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::getIntParameter(evs::CameraParam id,
std::vector<int32_t>* value) {
const auto it = mParams.find(id);
if (it == mParams.end()) {
return ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int>(EvsResult::NOT_SUPPORTED));
}
value->push_back(it->second->value);
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::getIntParameterRange(evs::CameraParam id,
evs::ParameterRange* _aidl_return) {
const auto it = mParams.find(id);
if (it == mParams.end()) {
return ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int>(EvsResult::NOT_SUPPORTED));
}
_aidl_return->min = it->second->range.min;
_aidl_return->max = it->second->range.max;
_aidl_return->step = it->second->range.step;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::getParameterList(
std::vector<evs::CameraParam>* _aidl_return) {
if (mCameraInfo) {
_aidl_return->resize(mCameraInfo->controls.size());
std::size_t idx = 0;
for (const auto& [name, range] : mCameraInfo->controls) {
(*_aidl_return)[idx++] = name;
}
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::getPhysicalCameraInfo(const std::string& /* deviceId */,
evs::CameraDesc* _aidl_return) {
return getCameraInfo(_aidl_return);
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::setExtendedInfo(
int32_t opaqueIdentifier, const std::vector<uint8_t>& opaqueValue) {
mExtInfo.insert_or_assign(opaqueIdentifier, opaqueValue);
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::setIntParameter(evs::CameraParam id, int32_t value,
std::vector<int32_t>* effectiveValue) {
const auto it = mParams.find(id);
if (it == mParams.end()) {
return ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int>(EvsResult::NOT_SUPPORTED));
}
// Rounding down to the closest value.
int32_t candidate = value / it->second->range.step * it->second->range.step;
if (candidate < it->second->range.min || candidate > it->second->range.max) {
return ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int>(EvsResult::INVALID_ARG));
}
it->second->value = candidate;
effectiveValue->push_back(candidate);
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::setPrimaryClient() {
/* Because EVS HW module reference implementation expects a single client at
* a time, this returns a success code always.
*/
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::unsetPrimaryClient() {
/* Because EVS HW module reference implementation expects a single client at
* a time, there is no chance that this is called by the secondary client and
* therefore returns a success code always.
*/
return ndk::ScopedAStatus::ok();
}
std::shared_ptr<EvsVideoEmulatedCamera> EvsVideoEmulatedCamera::Create(const char* deviceName) {
std::unique_ptr<ConfigManager::CameraInfo> nullCamInfo = nullptr;
return Create(deviceName, nullCamInfo);
}
std::shared_ptr<EvsVideoEmulatedCamera> EvsVideoEmulatedCamera::Create(
const char* deviceName, std::unique_ptr<ConfigManager::CameraInfo>& camInfo,
const evs::Stream* /* streamCfg */) {
std::shared_ptr<EvsVideoEmulatedCamera> c =
ndk::SharedRefBase::make<EvsVideoEmulatedCamera>(Sigil{}, deviceName, camInfo);
if (!c) {
LOG(ERROR) << "Failed to instantiate EvsVideoEmulatedCamera.";
return nullptr;
}
if (!c->initialize()) {
LOG(ERROR) << "Failed to initialize EvsVideoEmulatedCamera.";
return nullptr;
}
return c;
}
void EvsVideoEmulatedCamera::shutdown() {
mVideoCodec.reset();
mVideoExtractor.reset();
close(mVideoFd);
mVideoFd = 0;
Base::shutdown();
}
} // namespace aidl::android::hardware::automotive::evs::implementation