mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 11:36:00 +00:00
Camera: add webcam offline processing support
Test: CTS/VTS in follow up changes Bug: 135142453 Change-Id: Ie95f22795f4807ed384443de0fffc65c5dde16d1
This commit is contained in:
@@ -81,8 +81,6 @@ bool tryLock(std::mutex& mutex)
|
||||
return locked;
|
||||
}
|
||||
|
||||
buffer_handle_t sEmptyBuffer = nullptr;
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
// Static instances
|
||||
@@ -119,8 +117,8 @@ bool ExternalCameraDeviceSession::initialize() {
|
||||
std::string make, model;
|
||||
if (ret < 0) {
|
||||
ALOGW("%s v4l2 QUERYCAP failed", __FUNCTION__);
|
||||
make = "Generic UVC webcam";
|
||||
model = "Generic UVC webcam";
|
||||
mExifMake = "Generic UVC webcam";
|
||||
mExifModel = "Generic UVC webcam";
|
||||
} else {
|
||||
// capability.card is UTF-8 encoded
|
||||
char card[32];
|
||||
@@ -134,11 +132,11 @@ bool ExternalCameraDeviceSession::initialize() {
|
||||
}
|
||||
}
|
||||
if (j == 0 || card[j - 1] != '\0') {
|
||||
make = "Generic UVC webcam";
|
||||
model = "Generic UVC webcam";
|
||||
mExifMake = "Generic UVC webcam";
|
||||
mExifModel = "Generic UVC webcam";
|
||||
} else {
|
||||
make = card;
|
||||
model = card;
|
||||
mExifMake = card;
|
||||
mExifModel = card;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +145,7 @@ bool ExternalCameraDeviceSession::initialize() {
|
||||
ALOGE("%s: init OutputThread failed!", __FUNCTION__);
|
||||
return true;
|
||||
}
|
||||
mOutputThread->setExifMakeModel(make, model);
|
||||
mOutputThread->setExifMakeModel(mExifMake, mExifModel);
|
||||
|
||||
status_t status = initDefaultRequests();
|
||||
if (status != OK) {
|
||||
@@ -161,7 +159,7 @@ bool ExternalCameraDeviceSession::initialize() {
|
||||
ALOGE("%s: invalid request fmq", __FUNCTION__);
|
||||
return true;
|
||||
}
|
||||
mResultMetadataQueue = std::make_shared<RequestMetadataQueue>(
|
||||
mResultMetadataQueue = std::make_shared<ResultMetadataQueue>(
|
||||
kMetadataMsgQueueSize, false /* non blocking */);
|
||||
if (!mResultMetadataQueue->isValid()) {
|
||||
ALOGE("%s: invalid result fmq", __FUNCTION__);
|
||||
@@ -183,7 +181,7 @@ bool ExternalCameraDeviceSession::isInitFailed() {
|
||||
}
|
||||
|
||||
void ExternalCameraDeviceSession::initOutputThread() {
|
||||
mOutputThread = new OutputThread(this, mCroppingType);
|
||||
mOutputThread = new OutputThread(this, mCroppingType, mCameraCharacteristics);
|
||||
}
|
||||
|
||||
void ExternalCameraDeviceSession::closeOutputThread() {
|
||||
@@ -518,35 +516,9 @@ Status ExternalCameraDeviceSession::importBufferLocked(int32_t streamId,
|
||||
uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr,
|
||||
bool allowEmptyBuf) {
|
||||
|
||||
if (buf == nullptr && bufId == BUFFER_ID_NO_BUFFER) {
|
||||
if (allowEmptyBuf) {
|
||||
*outBufPtr = &sEmptyBuffer;
|
||||
return Status::OK;
|
||||
} else {
|
||||
ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId);
|
||||
return Status::ILLEGAL_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
CirculatingBuffers& cbs = mCirculatingBuffers[streamId];
|
||||
if (cbs.count(bufId) == 0) {
|
||||
if (buf == nullptr) {
|
||||
ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId);
|
||||
return Status::ILLEGAL_ARGUMENT;
|
||||
}
|
||||
// Register a newly seen buffer
|
||||
buffer_handle_t importedBuf = buf;
|
||||
sHandleImporter.importBuffer(importedBuf);
|
||||
if (importedBuf == nullptr) {
|
||||
ALOGE("%s: output buffer for stream %d is invalid!", __FUNCTION__, streamId);
|
||||
return Status::INTERNAL_ERROR;
|
||||
} else {
|
||||
cbs[bufId] = importedBuf;
|
||||
}
|
||||
}
|
||||
*outBufPtr = &cbs[bufId];
|
||||
return Status::OK;
|
||||
return importBufferImpl(
|
||||
mCirculatingBuffers, sHandleImporter, streamId,
|
||||
bufId, buf, outBufPtr, allowEmptyBuf);
|
||||
}
|
||||
|
||||
Status ExternalCameraDeviceSession::importRequestLockedImpl(
|
||||
@@ -791,15 +763,32 @@ void ExternalCameraDeviceSession::notifyError(
|
||||
|
||||
//TODO: refactor with processCaptureResult
|
||||
Status ExternalCameraDeviceSession::processCaptureRequestError(
|
||||
const std::shared_ptr<HalRequest>& req) {
|
||||
const std::shared_ptr<HalRequest>& req,
|
||||
/*out*/std::vector<NotifyMsg>* outMsgs,
|
||||
/*out*/std::vector<CaptureResult>* outResults) {
|
||||
ATRACE_CALL();
|
||||
// Return V4L2 buffer to V4L2 buffer queue
|
||||
enqueueV4l2Frame(req->frameIn);
|
||||
sp<V3_4::implementation::V4L2Frame> v4l2Frame =
|
||||
static_cast<V3_4::implementation::V4L2Frame*>(req->frameIn.get());
|
||||
enqueueV4l2Frame(v4l2Frame);
|
||||
|
||||
// NotifyShutter
|
||||
notifyShutter(req->frameNumber, req->shutterTs);
|
||||
if (outMsgs == nullptr) {
|
||||
notifyShutter(req->frameNumber, req->shutterTs);
|
||||
notifyError(/*frameNum*/req->frameNumber, /*stream*/-1, ErrorCode::ERROR_REQUEST);
|
||||
} else {
|
||||
NotifyMsg shutter;
|
||||
shutter.type = MsgType::SHUTTER;
|
||||
shutter.msg.shutter.frameNumber = req->frameNumber;
|
||||
shutter.msg.shutter.timestamp = req->shutterTs;
|
||||
|
||||
notifyError(/*frameNum*/req->frameNumber, /*stream*/-1, ErrorCode::ERROR_REQUEST);
|
||||
NotifyMsg error;
|
||||
error.type = MsgType::ERROR;
|
||||
error.msg.error.frameNumber = req->frameNumber;
|
||||
error.msg.error.errorStreamId = -1;
|
||||
error.msg.error.errorCode = ErrorCode::ERROR_REQUEST;
|
||||
outMsgs->push_back(shutter);
|
||||
outMsgs->push_back(error);
|
||||
}
|
||||
|
||||
// Fill output buffers
|
||||
hidl_vec<CaptureResult> results;
|
||||
@@ -826,16 +815,22 @@ Status ExternalCameraDeviceSession::processCaptureRequestError(
|
||||
mInflightFrames.erase(req->frameNumber);
|
||||
}
|
||||
|
||||
// Callback into framework
|
||||
invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true);
|
||||
freeReleaseFences(results);
|
||||
if (outResults == nullptr) {
|
||||
// Callback into framework
|
||||
invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true);
|
||||
freeReleaseFences(results);
|
||||
} else {
|
||||
outResults->push_back(result);
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status ExternalCameraDeviceSession::processCaptureResult(std::shared_ptr<HalRequest>& req) {
|
||||
ATRACE_CALL();
|
||||
// Return V4L2 buffer to V4L2 buffer queue
|
||||
enqueueV4l2Frame(req->frameIn);
|
||||
sp<V3_4::implementation::V4L2Frame> v4l2Frame =
|
||||
static_cast<V3_4::implementation::V4L2Frame*>(req->frameIn.get());
|
||||
enqueueV4l2Frame(v4l2Frame);
|
||||
|
||||
// NotifyShutter
|
||||
notifyShutter(req->frameNumber, req->shutterTs);
|
||||
@@ -923,29 +918,10 @@ void ExternalCameraDeviceSession::invokeProcessCaptureResultCallback(
|
||||
mProcessCaptureResultLock.unlock();
|
||||
}
|
||||
|
||||
void ExternalCameraDeviceSession::freeReleaseFences(hidl_vec<CaptureResult>& results) {
|
||||
for (auto& result : results) {
|
||||
if (result.inputBuffer.releaseFence.getNativeHandle() != nullptr) {
|
||||
native_handle_t* handle = const_cast<native_handle_t*>(
|
||||
result.inputBuffer.releaseFence.getNativeHandle());
|
||||
native_handle_close(handle);
|
||||
native_handle_delete(handle);
|
||||
}
|
||||
for (auto& buf : result.outputBuffers) {
|
||||
if (buf.releaseFence.getNativeHandle() != nullptr) {
|
||||
native_handle_t* handle = const_cast<native_handle_t*>(
|
||||
buf.releaseFence.getNativeHandle());
|
||||
native_handle_close(handle);
|
||||
native_handle_delete(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalCameraDeviceSession::OutputThread::OutputThread(
|
||||
wp<ExternalCameraDeviceSession> parent,
|
||||
CroppingType ct) : mParent(parent), mCroppingType(ct) {}
|
||||
wp<OutputThreadInterface> parent, CroppingType ct,
|
||||
const common::V1_0::helper::CameraMetadata& chars) :
|
||||
mParent(parent), mCroppingType(ct), mCameraCharacteristics(chars) {}
|
||||
|
||||
ExternalCameraDeviceSession::OutputThread::~OutputThread() {}
|
||||
|
||||
@@ -955,88 +931,6 @@ void ExternalCameraDeviceSession::OutputThread::setExifMakeModel(
|
||||
mExifModel = model;
|
||||
}
|
||||
|
||||
uint32_t ExternalCameraDeviceSession::OutputThread::getFourCcFromLayout(
|
||||
const YCbCrLayout& layout) {
|
||||
intptr_t cb = reinterpret_cast<intptr_t>(layout.cb);
|
||||
intptr_t cr = reinterpret_cast<intptr_t>(layout.cr);
|
||||
if (std::abs(cb - cr) == 1 && layout.chromaStep == 2) {
|
||||
// Interleaved format
|
||||
if (layout.cb > layout.cr) {
|
||||
return V4L2_PIX_FMT_NV21;
|
||||
} else {
|
||||
return V4L2_PIX_FMT_NV12;
|
||||
}
|
||||
} else if (layout.chromaStep == 1) {
|
||||
// Planar format
|
||||
if (layout.cb > layout.cr) {
|
||||
return V4L2_PIX_FMT_YVU420; // YV12
|
||||
} else {
|
||||
return V4L2_PIX_FMT_YUV420; // YU12
|
||||
}
|
||||
} else {
|
||||
return FLEX_YUV_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
int ExternalCameraDeviceSession::OutputThread::getCropRect(
|
||||
CroppingType ct, const Size& inSize, const Size& outSize, IMapper::Rect* out) {
|
||||
if (out == nullptr) {
|
||||
ALOGE("%s: out is null", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t inW = inSize.width;
|
||||
uint32_t inH = inSize.height;
|
||||
uint32_t outW = outSize.width;
|
||||
uint32_t outH = outSize.height;
|
||||
|
||||
// Handle special case where aspect ratio is close to input but scaled
|
||||
// dimension is slightly larger than input
|
||||
float arIn = ASPECT_RATIO(inSize);
|
||||
float arOut = ASPECT_RATIO(outSize);
|
||||
if (isAspectRatioClose(arIn, arOut)) {
|
||||
out->left = 0;
|
||||
out->top = 0;
|
||||
out->width = inW;
|
||||
out->height = inH;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ct == VERTICAL) {
|
||||
uint64_t scaledOutH = static_cast<uint64_t>(outH) * inW / outW;
|
||||
if (scaledOutH > inH) {
|
||||
ALOGE("%s: Output size %dx%d cannot be vertically cropped from input size %dx%d",
|
||||
__FUNCTION__, outW, outH, inW, inH);
|
||||
return -1;
|
||||
}
|
||||
scaledOutH = scaledOutH & ~0x1; // make it multiple of 2
|
||||
|
||||
out->left = 0;
|
||||
out->top = ((inH - scaledOutH) / 2) & ~0x1;
|
||||
out->width = inW;
|
||||
out->height = static_cast<int32_t>(scaledOutH);
|
||||
ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledH %d",
|
||||
__FUNCTION__, inW, inH, outW, outH, out->top, static_cast<int32_t>(scaledOutH));
|
||||
} else {
|
||||
uint64_t scaledOutW = static_cast<uint64_t>(outW) * inH / outH;
|
||||
if (scaledOutW > inW) {
|
||||
ALOGE("%s: Output size %dx%d cannot be horizontally cropped from input size %dx%d",
|
||||
__FUNCTION__, outW, outH, inW, inH);
|
||||
return -1;
|
||||
}
|
||||
scaledOutW = scaledOutW & ~0x1; // make it multiple of 2
|
||||
|
||||
out->left = ((inW - scaledOutW) / 2) & ~0x1;
|
||||
out->top = 0;
|
||||
out->width = static_cast<int32_t>(scaledOutW);
|
||||
out->height = inH;
|
||||
ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledW %d",
|
||||
__FUNCTION__, inW, inH, outW, outH, out->top, static_cast<int32_t>(scaledOutW));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ExternalCameraDeviceSession::OutputThread::cropAndScaleLocked(
|
||||
sp<AllocatedFrame>& in, const Size& outSz, YCbCrLayout* out) {
|
||||
Size inSz = {in->mWidth, in->mHeight};
|
||||
@@ -1274,265 +1168,6 @@ int ExternalCameraDeviceSession::OutputThread::cropAndScaleThumbLocked(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ExternalCameraDeviceSession::OutputThread::formatConvertLocked(
|
||||
const YCbCrLayout& in, const YCbCrLayout& out, Size sz, uint32_t format) {
|
||||
int ret = 0;
|
||||
switch (format) {
|
||||
case V4L2_PIX_FMT_NV21:
|
||||
ret = libyuv::I420ToNV21(
|
||||
static_cast<uint8_t*>(in.y),
|
||||
in.yStride,
|
||||
static_cast<uint8_t*>(in.cb),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(in.cr),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(out.y),
|
||||
out.yStride,
|
||||
static_cast<uint8_t*>(out.cr),
|
||||
out.cStride,
|
||||
sz.width,
|
||||
sz.height);
|
||||
if (ret != 0) {
|
||||
ALOGE("%s: convert to NV21 buffer failed! ret %d",
|
||||
__FUNCTION__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case V4L2_PIX_FMT_NV12:
|
||||
ret = libyuv::I420ToNV12(
|
||||
static_cast<uint8_t*>(in.y),
|
||||
in.yStride,
|
||||
static_cast<uint8_t*>(in.cb),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(in.cr),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(out.y),
|
||||
out.yStride,
|
||||
static_cast<uint8_t*>(out.cb),
|
||||
out.cStride,
|
||||
sz.width,
|
||||
sz.height);
|
||||
if (ret != 0) {
|
||||
ALOGE("%s: convert to NV12 buffer failed! ret %d",
|
||||
__FUNCTION__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case V4L2_PIX_FMT_YVU420: // YV12
|
||||
case V4L2_PIX_FMT_YUV420: // YU12
|
||||
// TODO: maybe we can speed up here by somehow save this copy?
|
||||
ret = libyuv::I420Copy(
|
||||
static_cast<uint8_t*>(in.y),
|
||||
in.yStride,
|
||||
static_cast<uint8_t*>(in.cb),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(in.cr),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(out.y),
|
||||
out.yStride,
|
||||
static_cast<uint8_t*>(out.cb),
|
||||
out.cStride,
|
||||
static_cast<uint8_t*>(out.cr),
|
||||
out.cStride,
|
||||
sz.width,
|
||||
sz.height);
|
||||
if (ret != 0) {
|
||||
ALOGE("%s: copy to YV12 or YU12 buffer failed! ret %d",
|
||||
__FUNCTION__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case FLEX_YUV_GENERIC:
|
||||
// TODO: b/72261744 write to arbitrary flexible YUV layout. Slow.
|
||||
ALOGE("%s: unsupported flexible yuv layout"
|
||||
" y %p cb %p cr %p y_str %d c_str %d c_step %d",
|
||||
__FUNCTION__, out.y, out.cb, out.cr,
|
||||
out.yStride, out.cStride, out.chromaStep);
|
||||
return -1;
|
||||
default:
|
||||
ALOGE("%s: unknown YUV format 0x%x!", __FUNCTION__, format);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ExternalCameraDeviceSession::OutputThread::encodeJpegYU12(
|
||||
const Size & inSz, const YCbCrLayout& inLayout,
|
||||
int jpegQuality, const void *app1Buffer, size_t app1Size,
|
||||
void *out, const size_t maxOutSize, size_t &actualCodeSize)
|
||||
{
|
||||
/* libjpeg is a C library so we use C-style "inheritance" by
|
||||
* putting libjpeg's jpeg_destination_mgr first in our custom
|
||||
* struct. This allows us to cast jpeg_destination_mgr* to
|
||||
* CustomJpegDestMgr* when we get it passed to us in a callback */
|
||||
struct CustomJpegDestMgr {
|
||||
struct jpeg_destination_mgr mgr;
|
||||
JOCTET *mBuffer;
|
||||
size_t mBufferSize;
|
||||
size_t mEncodedSize;
|
||||
bool mSuccess;
|
||||
} dmgr;
|
||||
|
||||
jpeg_compress_struct cinfo = {};
|
||||
jpeg_error_mgr jerr;
|
||||
|
||||
/* Initialize error handling with standard callbacks, but
|
||||
* then override output_message (to print to ALOG) and
|
||||
* error_exit to set a flag and print a message instead
|
||||
* of killing the whole process */
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
|
||||
cinfo.err->output_message = [](j_common_ptr cinfo) {
|
||||
char buffer[JMSG_LENGTH_MAX];
|
||||
|
||||
/* Create the message */
|
||||
(*cinfo->err->format_message)(cinfo, buffer);
|
||||
ALOGE("libjpeg error: %s", buffer);
|
||||
};
|
||||
cinfo.err->error_exit = [](j_common_ptr cinfo) {
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
if(cinfo->client_data) {
|
||||
auto & dmgr =
|
||||
*reinterpret_cast<CustomJpegDestMgr*>(cinfo->client_data);
|
||||
dmgr.mSuccess = false;
|
||||
}
|
||||
};
|
||||
/* Now that we initialized some callbacks, let's create our compressor */
|
||||
jpeg_create_compress(&cinfo);
|
||||
|
||||
/* Initialize our destination manager */
|
||||
dmgr.mBuffer = static_cast<JOCTET*>(out);
|
||||
dmgr.mBufferSize = maxOutSize;
|
||||
dmgr.mEncodedSize = 0;
|
||||
dmgr.mSuccess = true;
|
||||
cinfo.client_data = static_cast<void*>(&dmgr);
|
||||
|
||||
/* These lambdas become C-style function pointers and as per C++11 spec
|
||||
* may not capture anything */
|
||||
dmgr.mgr.init_destination = [](j_compress_ptr cinfo) {
|
||||
auto & dmgr = reinterpret_cast<CustomJpegDestMgr&>(*cinfo->dest);
|
||||
dmgr.mgr.next_output_byte = dmgr.mBuffer;
|
||||
dmgr.mgr.free_in_buffer = dmgr.mBufferSize;
|
||||
ALOGV("%s:%d jpeg start: %p [%zu]",
|
||||
__FUNCTION__, __LINE__, dmgr.mBuffer, dmgr.mBufferSize);
|
||||
};
|
||||
|
||||
dmgr.mgr.empty_output_buffer = [](j_compress_ptr cinfo __unused) {
|
||||
ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__);
|
||||
return 0;
|
||||
};
|
||||
|
||||
dmgr.mgr.term_destination = [](j_compress_ptr cinfo) {
|
||||
auto & dmgr = reinterpret_cast<CustomJpegDestMgr&>(*cinfo->dest);
|
||||
dmgr.mEncodedSize = dmgr.mBufferSize - dmgr.mgr.free_in_buffer;
|
||||
ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, dmgr.mEncodedSize);
|
||||
};
|
||||
cinfo.dest = reinterpret_cast<struct jpeg_destination_mgr*>(&dmgr);
|
||||
|
||||
/* We are going to be using JPEG in raw data mode, so we are passing
|
||||
* straight subsampled planar YCbCr and it will not touch our pixel
|
||||
* data or do any scaling or anything */
|
||||
cinfo.image_width = inSz.width;
|
||||
cinfo.image_height = inSz.height;
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_YCbCr;
|
||||
|
||||
/* Initialize defaults and then override what we want */
|
||||
jpeg_set_defaults(&cinfo);
|
||||
|
||||
jpeg_set_quality(&cinfo, jpegQuality, 1);
|
||||
jpeg_set_colorspace(&cinfo, JCS_YCbCr);
|
||||
cinfo.raw_data_in = 1;
|
||||
cinfo.dct_method = JDCT_IFAST;
|
||||
|
||||
/* Configure sampling factors. The sampling factor is JPEG subsampling 420
|
||||
* because the source format is YUV420. Note that libjpeg sampling factors
|
||||
* are... a little weird. Sampling of Y=2,U=1,V=1 means there is 1 U and
|
||||
* 1 V value for each 2 Y values */
|
||||
cinfo.comp_info[0].h_samp_factor = 2;
|
||||
cinfo.comp_info[0].v_samp_factor = 2;
|
||||
cinfo.comp_info[1].h_samp_factor = 1;
|
||||
cinfo.comp_info[1].v_samp_factor = 1;
|
||||
cinfo.comp_info[2].h_samp_factor = 1;
|
||||
cinfo.comp_info[2].v_samp_factor = 1;
|
||||
|
||||
/* Let's not hardcode YUV420 in 6 places... 5 was enough */
|
||||
int maxVSampFactor = std::max( {
|
||||
cinfo.comp_info[0].v_samp_factor,
|
||||
cinfo.comp_info[1].v_samp_factor,
|
||||
cinfo.comp_info[2].v_samp_factor
|
||||
});
|
||||
int cVSubSampling = cinfo.comp_info[0].v_samp_factor /
|
||||
cinfo.comp_info[1].v_samp_factor;
|
||||
|
||||
/* Start the compressor */
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
|
||||
/* Compute our macroblock height, so we can pad our input to be vertically
|
||||
* macroblock aligned.
|
||||
* TODO: Does it need to be horizontally MCU aligned too? */
|
||||
|
||||
size_t mcuV = DCTSIZE*maxVSampFactor;
|
||||
size_t paddedHeight = mcuV * ((inSz.height + mcuV - 1) / mcuV);
|
||||
|
||||
/* libjpeg uses arrays of row pointers, which makes it really easy to pad
|
||||
* data vertically (unfortunately doesn't help horizontally) */
|
||||
std::vector<JSAMPROW> yLines (paddedHeight);
|
||||
std::vector<JSAMPROW> cbLines(paddedHeight/cVSubSampling);
|
||||
std::vector<JSAMPROW> crLines(paddedHeight/cVSubSampling);
|
||||
|
||||
uint8_t *py = static_cast<uint8_t*>(inLayout.y);
|
||||
uint8_t *pcr = static_cast<uint8_t*>(inLayout.cr);
|
||||
uint8_t *pcb = static_cast<uint8_t*>(inLayout.cb);
|
||||
|
||||
for(uint32_t i = 0; i < paddedHeight; i++)
|
||||
{
|
||||
/* Once we are in the padding territory we still point to the last line
|
||||
* effectively replicating it several times ~ CLAMP_TO_EDGE */
|
||||
int li = std::min(i, inSz.height - 1);
|
||||
yLines[i] = static_cast<JSAMPROW>(py + li * inLayout.yStride);
|
||||
if(i < paddedHeight / cVSubSampling)
|
||||
{
|
||||
crLines[i] = static_cast<JSAMPROW>(pcr + li * inLayout.cStride);
|
||||
cbLines[i] = static_cast<JSAMPROW>(pcb + li * inLayout.cStride);
|
||||
}
|
||||
}
|
||||
|
||||
/* If APP1 data was passed in, use it */
|
||||
if(app1Buffer && app1Size)
|
||||
{
|
||||
jpeg_write_marker(&cinfo, JPEG_APP0 + 1,
|
||||
static_cast<const JOCTET*>(app1Buffer), app1Size);
|
||||
}
|
||||
|
||||
/* While we still have padded height left to go, keep giving it one
|
||||
* macroblock at a time. */
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
const uint32_t batchSize = DCTSIZE * maxVSampFactor;
|
||||
const uint32_t nl = cinfo.next_scanline;
|
||||
JSAMPARRAY planes[3]{ &yLines[nl],
|
||||
&cbLines[nl/cVSubSampling],
|
||||
&crLines[nl/cVSubSampling] };
|
||||
|
||||
uint32_t done = jpeg_write_raw_data(&cinfo, planes, batchSize);
|
||||
|
||||
if (done != batchSize) {
|
||||
ALOGE("%s: compressed %u lines, expected %u (total %u/%u)",
|
||||
__FUNCTION__, done, batchSize, cinfo.next_scanline,
|
||||
cinfo.image_height);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* This will flush everything */
|
||||
jpeg_finish_compress(&cinfo);
|
||||
|
||||
/* Grab the actual code size and set it */
|
||||
actualCodeSize = dmgr.mEncodedSize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: There needs to be a mechanism to discover allocated buffer size
|
||||
* in the HAL.
|
||||
@@ -1555,25 +1190,9 @@ Size ExternalCameraDeviceSession::getMaxJpegResolution() const {
|
||||
}
|
||||
|
||||
Size ExternalCameraDeviceSession::getMaxThumbResolution() const {
|
||||
Size thumbSize { 0, 0 };
|
||||
camera_metadata_ro_entry entry =
|
||||
mCameraCharacteristics.find(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES);
|
||||
for(uint32_t i = 0; i < entry.count; i += 2) {
|
||||
Size sz { static_cast<uint32_t>(entry.data.i32[i]),
|
||||
static_cast<uint32_t>(entry.data.i32[i+1]) };
|
||||
if(sz.width * sz.height > thumbSize.width * thumbSize.height) {
|
||||
thumbSize = sz;
|
||||
}
|
||||
}
|
||||
|
||||
if (thumbSize.width * thumbSize.height == 0) {
|
||||
ALOGW("%s: non-zero thumbnail size not available", __FUNCTION__);
|
||||
}
|
||||
|
||||
return thumbSize;
|
||||
return getMaxThumbnailResolution(mCameraCharacteristics);
|
||||
}
|
||||
|
||||
|
||||
ssize_t ExternalCameraDeviceSession::getJpegBufferSize(
|
||||
uint32_t width, uint32_t height) const {
|
||||
// Constant from camera3.h
|
||||
@@ -1616,7 +1235,7 @@ ssize_t ExternalCameraDeviceSession::getJpegBufferSize(
|
||||
|
||||
int ExternalCameraDeviceSession::OutputThread::createJpegLocked(
|
||||
HalStreamBuffer &halBuf,
|
||||
const std::shared_ptr<HalRequest>& req)
|
||||
const common::V1_0::helper::CameraMetadata& setting)
|
||||
{
|
||||
ATRACE_CALL();
|
||||
int ret;
|
||||
@@ -1645,17 +1264,17 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked(
|
||||
Size thumbSize;
|
||||
bool outputThumbnail = true;
|
||||
|
||||
if (req->setting.exists(ANDROID_JPEG_QUALITY)) {
|
||||
camera_metadata_entry entry =
|
||||
req->setting.find(ANDROID_JPEG_QUALITY);
|
||||
if (setting.exists(ANDROID_JPEG_QUALITY)) {
|
||||
camera_metadata_ro_entry entry =
|
||||
setting.find(ANDROID_JPEG_QUALITY);
|
||||
jpegQuality = entry.data.u8[0];
|
||||
} else {
|
||||
return lfail("%s: ANDROID_JPEG_QUALITY not set",__FUNCTION__);
|
||||
}
|
||||
|
||||
if (req->setting.exists(ANDROID_JPEG_THUMBNAIL_QUALITY)) {
|
||||
camera_metadata_entry entry =
|
||||
req->setting.find(ANDROID_JPEG_THUMBNAIL_QUALITY);
|
||||
if (setting.exists(ANDROID_JPEG_THUMBNAIL_QUALITY)) {
|
||||
camera_metadata_ro_entry entry =
|
||||
setting.find(ANDROID_JPEG_THUMBNAIL_QUALITY);
|
||||
thumbQuality = entry.data.u8[0];
|
||||
} else {
|
||||
return lfail(
|
||||
@@ -1663,9 +1282,9 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked(
|
||||
__FUNCTION__);
|
||||
}
|
||||
|
||||
if (req->setting.exists(ANDROID_JPEG_THUMBNAIL_SIZE)) {
|
||||
camera_metadata_entry entry =
|
||||
req->setting.find(ANDROID_JPEG_THUMBNAIL_SIZE);
|
||||
if (setting.exists(ANDROID_JPEG_THUMBNAIL_SIZE)) {
|
||||
camera_metadata_ro_entry entry =
|
||||
setting.find(ANDROID_JPEG_THUMBNAIL_SIZE);
|
||||
thumbSize = Size { static_cast<uint32_t>(entry.data.i32[0]),
|
||||
static_cast<uint32_t>(entry.data.i32[1])
|
||||
};
|
||||
@@ -1732,8 +1351,8 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked(
|
||||
|
||||
/* Combine camera characteristics with request settings to form EXIF
|
||||
* metadata */
|
||||
common::V1_0::helper::CameraMetadata meta(parent->mCameraCharacteristics);
|
||||
meta.append(req->setting);
|
||||
common::V1_0::helper::CameraMetadata meta(mCameraCharacteristics);
|
||||
meta.append(setting);
|
||||
|
||||
/* Generate EXIF object */
|
||||
std::unique_ptr<ExifUtils> utils(ExifUtils::create());
|
||||
@@ -1838,7 +1457,7 @@ bool ExternalCameraDeviceSession::OutputThread::threadLoop() {
|
||||
// TODO: see if we can save some computation by converting to YV12 here
|
||||
uint8_t* inData;
|
||||
size_t inDataSize;
|
||||
if (req->frameIn->map(&inData, &inDataSize) != 0) {
|
||||
if (req->frameIn->getData(&inData, &inDataSize) != 0) {
|
||||
lk.unlock();
|
||||
return onDeviceError("%s: V4L2 buffer map failed", __FUNCTION__);
|
||||
}
|
||||
@@ -1899,7 +1518,7 @@ bool ExternalCameraDeviceSession::OutputThread::threadLoop() {
|
||||
// Gralloc lockYCbCr the buffer
|
||||
switch (halBuf.format) {
|
||||
case PixelFormat::BLOB: {
|
||||
int ret = createJpegLocked(halBuf, req);
|
||||
int ret = createJpegLocked(halBuf, req->setting);
|
||||
|
||||
if(ret != 0) {
|
||||
lk.unlock();
|
||||
@@ -1949,8 +1568,8 @@ bool ExternalCameraDeviceSession::OutputThread::threadLoop() {
|
||||
}
|
||||
|
||||
Size sz {halBuf.width, halBuf.height};
|
||||
ATRACE_BEGIN("formatConvertLocked");
|
||||
ret = formatConvertLocked(cropAndScaled, outLayout, sz, outputFourcc);
|
||||
ATRACE_BEGIN("formatConvert");
|
||||
ret = formatConvert(cropAndScaled, outLayout, sz, outputFourcc);
|
||||
ATRACE_END();
|
||||
if (ret != 0) {
|
||||
lk.unlock();
|
||||
@@ -2055,6 +1674,14 @@ Status ExternalCameraDeviceSession::OutputThread::allocateIntermediateBuffers(
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
void ExternalCameraDeviceSession::OutputThread::clearIntermediateBuffers() {
|
||||
std::lock_guard<std::mutex> lk(mBufferLock);
|
||||
mYu12Frame.clear();
|
||||
mYu12ThumbFrame.clear();
|
||||
mIntermediateBuffers.clear();
|
||||
mBlobBufferSize = 0;
|
||||
}
|
||||
|
||||
Status ExternalCameraDeviceSession::OutputThread::submitRequest(
|
||||
const std::shared_ptr<HalRequest>& req) {
|
||||
std::unique_lock<std::mutex> lk(mRequestListLock);
|
||||
@@ -2090,6 +1717,32 @@ void ExternalCameraDeviceSession::OutputThread::flush() {
|
||||
}
|
||||
}
|
||||
|
||||
std::list<std::shared_ptr<HalRequest>>
|
||||
ExternalCameraDeviceSession::OutputThread::switchToOffline() {
|
||||
ATRACE_CALL();
|
||||
std::list<std::shared_ptr<HalRequest>> emptyList;
|
||||
auto parent = mParent.promote();
|
||||
if (parent == nullptr) {
|
||||
ALOGE("%s: session has been disconnected!", __FUNCTION__);
|
||||
return emptyList;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lk(mRequestListLock);
|
||||
std::list<std::shared_ptr<HalRequest>> reqs = std::move(mRequestList);
|
||||
mRequestList.clear();
|
||||
if (mProcessingRequest) {
|
||||
std::chrono::seconds timeout = std::chrono::seconds(kFlushWaitTimeoutSec);
|
||||
auto st = mRequestDoneCond.wait_for(lk, timeout);
|
||||
if (st == std::cv_status::timeout) {
|
||||
ALOGE("%s: wait for inflight request finish timeout!", __FUNCTION__);
|
||||
}
|
||||
}
|
||||
lk.unlock();
|
||||
clearIntermediateBuffers();
|
||||
ALOGV("%s: returning %zu request for offline processing", __FUNCTION__, reqs.size());
|
||||
return reqs;
|
||||
}
|
||||
|
||||
void ExternalCameraDeviceSession::OutputThread::waitForNextRequest(
|
||||
std::shared_ptr<HalRequest>* out) {
|
||||
ATRACE_CALL();
|
||||
@@ -2733,6 +2386,7 @@ Status ExternalCameraDeviceSession::configureStreams(
|
||||
return Status::INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
mBlobBufferSize = blobBufferSize;
|
||||
status = mOutputThread->allocateIntermediateBuffers(v4lSize,
|
||||
mMaxThumbResolution, config.streams, blobBufferSize);
|
||||
if (status != Status::OK) {
|
||||
@@ -2916,16 +2570,6 @@ status_t ExternalCameraDeviceSession::initDefaultRequests() {
|
||||
|
||||
status_t ExternalCameraDeviceSession::fillCaptureResult(
|
||||
common::V1_0::helper::CameraMetadata &md, nsecs_t timestamp) {
|
||||
// android.control
|
||||
// For USB camera, we don't know the AE state. Set the state to converged to
|
||||
// indicate the frame should be good to use. Then apps don't have to wait the
|
||||
// AE state.
|
||||
const uint8_t aeState = ANDROID_CONTROL_AE_STATE_CONVERGED;
|
||||
UPDATE(md, ANDROID_CONTROL_AE_STATE, &aeState, 1);
|
||||
|
||||
const uint8_t ae_lock = ANDROID_CONTROL_AE_LOCK_OFF;
|
||||
UPDATE(md, ANDROID_CONTROL_AE_LOCK, &ae_lock, 1);
|
||||
|
||||
bool afTrigger = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mAfTriggerLock);
|
||||
@@ -2951,46 +2595,10 @@ status_t ExternalCameraDeviceSession::fillCaptureResult(
|
||||
}
|
||||
UPDATE(md, ANDROID_CONTROL_AF_STATE, &afState, 1);
|
||||
|
||||
// Set AWB state to converged to indicate the frame should be good to use.
|
||||
const uint8_t awbState = ANDROID_CONTROL_AWB_STATE_CONVERGED;
|
||||
UPDATE(md, ANDROID_CONTROL_AWB_STATE, &awbState, 1);
|
||||
camera_metadata_ro_entry activeArraySize =
|
||||
mCameraCharacteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
|
||||
|
||||
const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF;
|
||||
UPDATE(md, ANDROID_CONTROL_AWB_LOCK, &awbLock, 1);
|
||||
|
||||
camera_metadata_ro_entry active_array_size =
|
||||
mCameraCharacteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
|
||||
|
||||
if (active_array_size.count == 0) {
|
||||
ALOGE("%s: cannot find active array size!", __FUNCTION__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
const uint8_t flashState = ANDROID_FLASH_STATE_UNAVAILABLE;
|
||||
UPDATE(md, ANDROID_FLASH_STATE, &flashState, 1);
|
||||
|
||||
// This means pipeline latency of X frame intervals. The maximum number is 4.
|
||||
const uint8_t requestPipelineMaxDepth = 4;
|
||||
UPDATE(md, ANDROID_REQUEST_PIPELINE_DEPTH, &requestPipelineMaxDepth, 1);
|
||||
|
||||
// android.scaler
|
||||
const int32_t crop_region[] = {
|
||||
active_array_size.data.i32[0], active_array_size.data.i32[1],
|
||||
active_array_size.data.i32[2], active_array_size.data.i32[3],
|
||||
};
|
||||
UPDATE(md, ANDROID_SCALER_CROP_REGION, crop_region, ARRAY_SIZE(crop_region));
|
||||
|
||||
// android.sensor
|
||||
UPDATE(md, ANDROID_SENSOR_TIMESTAMP, ×tamp, 1);
|
||||
|
||||
// android.statistics
|
||||
const uint8_t lensShadingMapMode = ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF;
|
||||
UPDATE(md, ANDROID_STATISTICS_LENS_SHADING_MAP_MODE, &lensShadingMapMode, 1);
|
||||
|
||||
const uint8_t sceneFlicker = ANDROID_STATISTICS_SCENE_FLICKER_NONE;
|
||||
UPDATE(md, ANDROID_STATISTICS_SCENE_FLICKER, &sceneFlicker, 1);
|
||||
|
||||
return OK;
|
||||
return fillCaptureResultCommon(md, timestamp, activeArraySize);
|
||||
}
|
||||
|
||||
#undef ARRAY_SIZE
|
||||
|
||||
@@ -18,10 +18,23 @@
|
||||
#include <log/log.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#define HAVE_JPEG // required for libyuv.h to export MJPEG decode APIs
|
||||
#include <libyuv.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
#include "ExternalCameraUtils.h"
|
||||
|
||||
namespace {
|
||||
|
||||
buffer_handle_t sEmptyBuffer = nullptr;
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
namespace camera {
|
||||
@@ -29,10 +42,13 @@ namespace device {
|
||||
namespace V3_4 {
|
||||
namespace implementation {
|
||||
|
||||
Frame::Frame(uint32_t width, uint32_t height, uint32_t fourcc) :
|
||||
mWidth(width), mHeight(height), mFourcc(fourcc) {}
|
||||
|
||||
V4L2Frame::V4L2Frame(
|
||||
uint32_t w, uint32_t h, uint32_t fourcc,
|
||||
int bufIdx, int fd, uint32_t dataSize, uint64_t offset) :
|
||||
mWidth(w), mHeight(h), mFourcc(fourcc),
|
||||
Frame(w, h, fourcc),
|
||||
mBufferIndex(bufIdx), mFd(fd), mDataSize(dataSize), mOffset(offset) {}
|
||||
|
||||
int V4L2Frame::map(uint8_t** data, size_t* dataSize) {
|
||||
@@ -75,9 +91,13 @@ V4L2Frame::~V4L2Frame() {
|
||||
unmap();
|
||||
}
|
||||
|
||||
int V4L2Frame::getData(uint8_t** outData, size_t* dataSize) {
|
||||
return map(outData, dataSize);
|
||||
}
|
||||
|
||||
AllocatedFrame::AllocatedFrame(
|
||||
uint32_t w, uint32_t h) :
|
||||
mWidth(w), mHeight(h), mFourcc(V4L2_PIX_FMT_YUV420) {};
|
||||
Frame(w, h, V4L2_PIX_FMT_YUV420) {};
|
||||
|
||||
AllocatedFrame::~AllocatedFrame() {}
|
||||
|
||||
@@ -106,6 +126,17 @@ int AllocatedFrame::allocate(YCbCrLayout* out) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AllocatedFrame::getData(uint8_t** outData, size_t* dataSize) {
|
||||
YCbCrLayout layout;
|
||||
int ret = allocate(&layout);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
*outData = mData.data();
|
||||
*dataSize = mData.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AllocatedFrame::getLayout(YCbCrLayout* out) {
|
||||
IMapper::Rect noCrop = {0, 0,
|
||||
static_cast<int32_t>(mWidth),
|
||||
@@ -150,8 +181,520 @@ double SupportedV4L2Format::FrameRate::getDouble() const {
|
||||
return durationDenominator / static_cast<double>(durationNumerator);
|
||||
}
|
||||
|
||||
::android::hardware::camera::common::V1_0::Status importBufferImpl(
|
||||
/*inout*/std::map<int, CirculatingBuffers>& circulatingBuffers,
|
||||
/*inout*/HandleImporter& handleImporter,
|
||||
int32_t streamId,
|
||||
uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr,
|
||||
bool allowEmptyBuf) {
|
||||
using ::android::hardware::camera::common::V1_0::Status;
|
||||
if (buf == nullptr && bufId == BUFFER_ID_NO_BUFFER) {
|
||||
if (allowEmptyBuf) {
|
||||
*outBufPtr = &sEmptyBuffer;
|
||||
return Status::OK;
|
||||
} else {
|
||||
ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId);
|
||||
return Status::ILLEGAL_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
CirculatingBuffers& cbs = circulatingBuffers[streamId];
|
||||
if (cbs.count(bufId) == 0) {
|
||||
if (buf == nullptr) {
|
||||
ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId);
|
||||
return Status::ILLEGAL_ARGUMENT;
|
||||
}
|
||||
// Register a newly seen buffer
|
||||
buffer_handle_t importedBuf = buf;
|
||||
handleImporter.importBuffer(importedBuf);
|
||||
if (importedBuf == nullptr) {
|
||||
ALOGE("%s: output buffer for stream %d is invalid!", __FUNCTION__, streamId);
|
||||
return Status::INTERNAL_ERROR;
|
||||
} else {
|
||||
cbs[bufId] = importedBuf;
|
||||
}
|
||||
}
|
||||
*outBufPtr = &cbs[bufId];
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
uint32_t getFourCcFromLayout(const YCbCrLayout& layout) {
|
||||
intptr_t cb = reinterpret_cast<intptr_t>(layout.cb);
|
||||
intptr_t cr = reinterpret_cast<intptr_t>(layout.cr);
|
||||
if (std::abs(cb - cr) == 1 && layout.chromaStep == 2) {
|
||||
// Interleaved format
|
||||
if (layout.cb > layout.cr) {
|
||||
return V4L2_PIX_FMT_NV21;
|
||||
} else {
|
||||
return V4L2_PIX_FMT_NV12;
|
||||
}
|
||||
} else if (layout.chromaStep == 1) {
|
||||
// Planar format
|
||||
if (layout.cb > layout.cr) {
|
||||
return V4L2_PIX_FMT_YVU420; // YV12
|
||||
} else {
|
||||
return V4L2_PIX_FMT_YUV420; // YU12
|
||||
}
|
||||
} else {
|
||||
return FLEX_YUV_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
int getCropRect(
|
||||
CroppingType ct, const Size& inSize, const Size& outSize, IMapper::Rect* out) {
|
||||
if (out == nullptr) {
|
||||
ALOGE("%s: out is null", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t inW = inSize.width;
|
||||
uint32_t inH = inSize.height;
|
||||
uint32_t outW = outSize.width;
|
||||
uint32_t outH = outSize.height;
|
||||
|
||||
// Handle special case where aspect ratio is close to input but scaled
|
||||
// dimension is slightly larger than input
|
||||
float arIn = ASPECT_RATIO(inSize);
|
||||
float arOut = ASPECT_RATIO(outSize);
|
||||
if (isAspectRatioClose(arIn, arOut)) {
|
||||
out->left = 0;
|
||||
out->top = 0;
|
||||
out->width = inW;
|
||||
out->height = inH;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ct == VERTICAL) {
|
||||
uint64_t scaledOutH = static_cast<uint64_t>(outH) * inW / outW;
|
||||
if (scaledOutH > inH) {
|
||||
ALOGE("%s: Output size %dx%d cannot be vertically cropped from input size %dx%d",
|
||||
__FUNCTION__, outW, outH, inW, inH);
|
||||
return -1;
|
||||
}
|
||||
scaledOutH = scaledOutH & ~0x1; // make it multiple of 2
|
||||
|
||||
out->left = 0;
|
||||
out->top = ((inH - scaledOutH) / 2) & ~0x1;
|
||||
out->width = inW;
|
||||
out->height = static_cast<int32_t>(scaledOutH);
|
||||
ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledH %d",
|
||||
__FUNCTION__, inW, inH, outW, outH, out->top, static_cast<int32_t>(scaledOutH));
|
||||
} else {
|
||||
uint64_t scaledOutW = static_cast<uint64_t>(outW) * inH / outH;
|
||||
if (scaledOutW > inW) {
|
||||
ALOGE("%s: Output size %dx%d cannot be horizontally cropped from input size %dx%d",
|
||||
__FUNCTION__, outW, outH, inW, inH);
|
||||
return -1;
|
||||
}
|
||||
scaledOutW = scaledOutW & ~0x1; // make it multiple of 2
|
||||
|
||||
out->left = ((inW - scaledOutW) / 2) & ~0x1;
|
||||
out->top = 0;
|
||||
out->width = static_cast<int32_t>(scaledOutW);
|
||||
out->height = inH;
|
||||
ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledW %d",
|
||||
__FUNCTION__, inW, inH, outW, outH, out->top, static_cast<int32_t>(scaledOutW));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int formatConvert(
|
||||
const YCbCrLayout& in, const YCbCrLayout& out, Size sz, uint32_t format) {
|
||||
int ret = 0;
|
||||
switch (format) {
|
||||
case V4L2_PIX_FMT_NV21:
|
||||
ret = libyuv::I420ToNV21(
|
||||
static_cast<uint8_t*>(in.y),
|
||||
in.yStride,
|
||||
static_cast<uint8_t*>(in.cb),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(in.cr),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(out.y),
|
||||
out.yStride,
|
||||
static_cast<uint8_t*>(out.cr),
|
||||
out.cStride,
|
||||
sz.width,
|
||||
sz.height);
|
||||
if (ret != 0) {
|
||||
ALOGE("%s: convert to NV21 buffer failed! ret %d",
|
||||
__FUNCTION__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case V4L2_PIX_FMT_NV12:
|
||||
ret = libyuv::I420ToNV12(
|
||||
static_cast<uint8_t*>(in.y),
|
||||
in.yStride,
|
||||
static_cast<uint8_t*>(in.cb),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(in.cr),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(out.y),
|
||||
out.yStride,
|
||||
static_cast<uint8_t*>(out.cb),
|
||||
out.cStride,
|
||||
sz.width,
|
||||
sz.height);
|
||||
if (ret != 0) {
|
||||
ALOGE("%s: convert to NV12 buffer failed! ret %d",
|
||||
__FUNCTION__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case V4L2_PIX_FMT_YVU420: // YV12
|
||||
case V4L2_PIX_FMT_YUV420: // YU12
|
||||
// TODO: maybe we can speed up here by somehow save this copy?
|
||||
ret = libyuv::I420Copy(
|
||||
static_cast<uint8_t*>(in.y),
|
||||
in.yStride,
|
||||
static_cast<uint8_t*>(in.cb),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(in.cr),
|
||||
in.cStride,
|
||||
static_cast<uint8_t*>(out.y),
|
||||
out.yStride,
|
||||
static_cast<uint8_t*>(out.cb),
|
||||
out.cStride,
|
||||
static_cast<uint8_t*>(out.cr),
|
||||
out.cStride,
|
||||
sz.width,
|
||||
sz.height);
|
||||
if (ret != 0) {
|
||||
ALOGE("%s: copy to YV12 or YU12 buffer failed! ret %d",
|
||||
__FUNCTION__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case FLEX_YUV_GENERIC:
|
||||
// TODO: b/72261744 write to arbitrary flexible YUV layout. Slow.
|
||||
ALOGE("%s: unsupported flexible yuv layout"
|
||||
" y %p cb %p cr %p y_str %d c_str %d c_step %d",
|
||||
__FUNCTION__, out.y, out.cb, out.cr,
|
||||
out.yStride, out.cStride, out.chromaStep);
|
||||
return -1;
|
||||
default:
|
||||
ALOGE("%s: unknown YUV format 0x%x!", __FUNCTION__, format);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int encodeJpegYU12(
|
||||
const Size & inSz, const YCbCrLayout& inLayout,
|
||||
int jpegQuality, const void *app1Buffer, size_t app1Size,
|
||||
void *out, const size_t maxOutSize, size_t &actualCodeSize)
|
||||
{
|
||||
/* libjpeg is a C library so we use C-style "inheritance" by
|
||||
* putting libjpeg's jpeg_destination_mgr first in our custom
|
||||
* struct. This allows us to cast jpeg_destination_mgr* to
|
||||
* CustomJpegDestMgr* when we get it passed to us in a callback */
|
||||
struct CustomJpegDestMgr {
|
||||
struct jpeg_destination_mgr mgr;
|
||||
JOCTET *mBuffer;
|
||||
size_t mBufferSize;
|
||||
size_t mEncodedSize;
|
||||
bool mSuccess;
|
||||
} dmgr;
|
||||
|
||||
jpeg_compress_struct cinfo = {};
|
||||
jpeg_error_mgr jerr;
|
||||
|
||||
/* Initialize error handling with standard callbacks, but
|
||||
* then override output_message (to print to ALOG) and
|
||||
* error_exit to set a flag and print a message instead
|
||||
* of killing the whole process */
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
|
||||
cinfo.err->output_message = [](j_common_ptr cinfo) {
|
||||
char buffer[JMSG_LENGTH_MAX];
|
||||
|
||||
/* Create the message */
|
||||
(*cinfo->err->format_message)(cinfo, buffer);
|
||||
ALOGE("libjpeg error: %s", buffer);
|
||||
};
|
||||
cinfo.err->error_exit = [](j_common_ptr cinfo) {
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
if(cinfo->client_data) {
|
||||
auto & dmgr =
|
||||
*reinterpret_cast<CustomJpegDestMgr*>(cinfo->client_data);
|
||||
dmgr.mSuccess = false;
|
||||
}
|
||||
};
|
||||
/* Now that we initialized some callbacks, let's create our compressor */
|
||||
jpeg_create_compress(&cinfo);
|
||||
|
||||
/* Initialize our destination manager */
|
||||
dmgr.mBuffer = static_cast<JOCTET*>(out);
|
||||
dmgr.mBufferSize = maxOutSize;
|
||||
dmgr.mEncodedSize = 0;
|
||||
dmgr.mSuccess = true;
|
||||
cinfo.client_data = static_cast<void*>(&dmgr);
|
||||
|
||||
/* These lambdas become C-style function pointers and as per C++11 spec
|
||||
* may not capture anything */
|
||||
dmgr.mgr.init_destination = [](j_compress_ptr cinfo) {
|
||||
auto & dmgr = reinterpret_cast<CustomJpegDestMgr&>(*cinfo->dest);
|
||||
dmgr.mgr.next_output_byte = dmgr.mBuffer;
|
||||
dmgr.mgr.free_in_buffer = dmgr.mBufferSize;
|
||||
ALOGV("%s:%d jpeg start: %p [%zu]",
|
||||
__FUNCTION__, __LINE__, dmgr.mBuffer, dmgr.mBufferSize);
|
||||
};
|
||||
|
||||
dmgr.mgr.empty_output_buffer = [](j_compress_ptr cinfo __unused) {
|
||||
ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__);
|
||||
return 0;
|
||||
};
|
||||
|
||||
dmgr.mgr.term_destination = [](j_compress_ptr cinfo) {
|
||||
auto & dmgr = reinterpret_cast<CustomJpegDestMgr&>(*cinfo->dest);
|
||||
dmgr.mEncodedSize = dmgr.mBufferSize - dmgr.mgr.free_in_buffer;
|
||||
ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, dmgr.mEncodedSize);
|
||||
};
|
||||
cinfo.dest = reinterpret_cast<struct jpeg_destination_mgr*>(&dmgr);
|
||||
|
||||
/* We are going to be using JPEG in raw data mode, so we are passing
|
||||
* straight subsampled planar YCbCr and it will not touch our pixel
|
||||
* data or do any scaling or anything */
|
||||
cinfo.image_width = inSz.width;
|
||||
cinfo.image_height = inSz.height;
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_YCbCr;
|
||||
|
||||
/* Initialize defaults and then override what we want */
|
||||
jpeg_set_defaults(&cinfo);
|
||||
|
||||
jpeg_set_quality(&cinfo, jpegQuality, 1);
|
||||
jpeg_set_colorspace(&cinfo, JCS_YCbCr);
|
||||
cinfo.raw_data_in = 1;
|
||||
cinfo.dct_method = JDCT_IFAST;
|
||||
|
||||
/* Configure sampling factors. The sampling factor is JPEG subsampling 420
|
||||
* because the source format is YUV420. Note that libjpeg sampling factors
|
||||
* are... a little weird. Sampling of Y=2,U=1,V=1 means there is 1 U and
|
||||
* 1 V value for each 2 Y values */
|
||||
cinfo.comp_info[0].h_samp_factor = 2;
|
||||
cinfo.comp_info[0].v_samp_factor = 2;
|
||||
cinfo.comp_info[1].h_samp_factor = 1;
|
||||
cinfo.comp_info[1].v_samp_factor = 1;
|
||||
cinfo.comp_info[2].h_samp_factor = 1;
|
||||
cinfo.comp_info[2].v_samp_factor = 1;
|
||||
|
||||
/* Let's not hardcode YUV420 in 6 places... 5 was enough */
|
||||
int maxVSampFactor = std::max( {
|
||||
cinfo.comp_info[0].v_samp_factor,
|
||||
cinfo.comp_info[1].v_samp_factor,
|
||||
cinfo.comp_info[2].v_samp_factor
|
||||
});
|
||||
int cVSubSampling = cinfo.comp_info[0].v_samp_factor /
|
||||
cinfo.comp_info[1].v_samp_factor;
|
||||
|
||||
/* Start the compressor */
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
|
||||
/* Compute our macroblock height, so we can pad our input to be vertically
|
||||
* macroblock aligned.
|
||||
* TODO: Does it need to be horizontally MCU aligned too? */
|
||||
|
||||
size_t mcuV = DCTSIZE*maxVSampFactor;
|
||||
size_t paddedHeight = mcuV * ((inSz.height + mcuV - 1) / mcuV);
|
||||
|
||||
/* libjpeg uses arrays of row pointers, which makes it really easy to pad
|
||||
* data vertically (unfortunately doesn't help horizontally) */
|
||||
std::vector<JSAMPROW> yLines (paddedHeight);
|
||||
std::vector<JSAMPROW> cbLines(paddedHeight/cVSubSampling);
|
||||
std::vector<JSAMPROW> crLines(paddedHeight/cVSubSampling);
|
||||
|
||||
uint8_t *py = static_cast<uint8_t*>(inLayout.y);
|
||||
uint8_t *pcr = static_cast<uint8_t*>(inLayout.cr);
|
||||
uint8_t *pcb = static_cast<uint8_t*>(inLayout.cb);
|
||||
|
||||
for(uint32_t i = 0; i < paddedHeight; i++)
|
||||
{
|
||||
/* Once we are in the padding territory we still point to the last line
|
||||
* effectively replicating it several times ~ CLAMP_TO_EDGE */
|
||||
int li = std::min(i, inSz.height - 1);
|
||||
yLines[i] = static_cast<JSAMPROW>(py + li * inLayout.yStride);
|
||||
if(i < paddedHeight / cVSubSampling)
|
||||
{
|
||||
crLines[i] = static_cast<JSAMPROW>(pcr + li * inLayout.cStride);
|
||||
cbLines[i] = static_cast<JSAMPROW>(pcb + li * inLayout.cStride);
|
||||
}
|
||||
}
|
||||
|
||||
/* If APP1 data was passed in, use it */
|
||||
if(app1Buffer && app1Size)
|
||||
{
|
||||
jpeg_write_marker(&cinfo, JPEG_APP0 + 1,
|
||||
static_cast<const JOCTET*>(app1Buffer), app1Size);
|
||||
}
|
||||
|
||||
/* While we still have padded height left to go, keep giving it one
|
||||
* macroblock at a time. */
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
const uint32_t batchSize = DCTSIZE * maxVSampFactor;
|
||||
const uint32_t nl = cinfo.next_scanline;
|
||||
JSAMPARRAY planes[3]{ &yLines[nl],
|
||||
&cbLines[nl/cVSubSampling],
|
||||
&crLines[nl/cVSubSampling] };
|
||||
|
||||
uint32_t done = jpeg_write_raw_data(&cinfo, planes, batchSize);
|
||||
|
||||
if (done != batchSize) {
|
||||
ALOGE("%s: compressed %u lines, expected %u (total %u/%u)",
|
||||
__FUNCTION__, done, batchSize, cinfo.next_scanline,
|
||||
cinfo.image_height);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* This will flush everything */
|
||||
jpeg_finish_compress(&cinfo);
|
||||
|
||||
/* Grab the actual code size and set it */
|
||||
actualCodeSize = dmgr.mEncodedSize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Size getMaxThumbnailResolution(const common::V1_0::helper::CameraMetadata& chars) {
|
||||
Size thumbSize { 0, 0 };
|
||||
camera_metadata_ro_entry entry =
|
||||
chars.find(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES);
|
||||
for(uint32_t i = 0; i < entry.count; i += 2) {
|
||||
Size sz { static_cast<uint32_t>(entry.data.i32[i]),
|
||||
static_cast<uint32_t>(entry.data.i32[i+1]) };
|
||||
if(sz.width * sz.height > thumbSize.width * thumbSize.height) {
|
||||
thumbSize = sz;
|
||||
}
|
||||
}
|
||||
|
||||
if (thumbSize.width * thumbSize.height == 0) {
|
||||
ALOGW("%s: non-zero thumbnail size not available", __FUNCTION__);
|
||||
}
|
||||
|
||||
return thumbSize;
|
||||
}
|
||||
|
||||
void freeReleaseFences(hidl_vec<V3_2::CaptureResult>& results) {
|
||||
for (auto& result : results) {
|
||||
if (result.inputBuffer.releaseFence.getNativeHandle() != nullptr) {
|
||||
native_handle_t* handle = const_cast<native_handle_t*>(
|
||||
result.inputBuffer.releaseFence.getNativeHandle());
|
||||
native_handle_close(handle);
|
||||
native_handle_delete(handle);
|
||||
}
|
||||
for (auto& buf : result.outputBuffers) {
|
||||
if (buf.releaseFence.getNativeHandle() != nullptr) {
|
||||
native_handle_t* handle = const_cast<native_handle_t*>(
|
||||
buf.releaseFence.getNativeHandle());
|
||||
native_handle_close(handle);
|
||||
native_handle_delete(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
#define UPDATE(md, tag, data, size) \
|
||||
do { \
|
||||
if ((md).update((tag), (data), (size))) { \
|
||||
ALOGE("Update " #tag " failed!"); \
|
||||
return BAD_VALUE; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
status_t fillCaptureResultCommon(
|
||||
common::V1_0::helper::CameraMetadata &md, nsecs_t timestamp,
|
||||
camera_metadata_ro_entry& activeArraySize) {
|
||||
if (activeArraySize.count < 4) {
|
||||
ALOGE("%s: cannot find active array size!", __FUNCTION__);
|
||||
return -EINVAL;
|
||||
}
|
||||
// android.control
|
||||
// For USB camera, we don't know the AE state. Set the state to converged to
|
||||
// indicate the frame should be good to use. Then apps don't have to wait the
|
||||
// AE state.
|
||||
const uint8_t aeState = ANDROID_CONTROL_AE_STATE_CONVERGED;
|
||||
UPDATE(md, ANDROID_CONTROL_AE_STATE, &aeState, 1);
|
||||
|
||||
const uint8_t ae_lock = ANDROID_CONTROL_AE_LOCK_OFF;
|
||||
UPDATE(md, ANDROID_CONTROL_AE_LOCK, &ae_lock, 1);
|
||||
|
||||
// Set AWB state to converged to indicate the frame should be good to use.
|
||||
const uint8_t awbState = ANDROID_CONTROL_AWB_STATE_CONVERGED;
|
||||
UPDATE(md, ANDROID_CONTROL_AWB_STATE, &awbState, 1);
|
||||
|
||||
const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF;
|
||||
UPDATE(md, ANDROID_CONTROL_AWB_LOCK, &awbLock, 1);
|
||||
|
||||
const uint8_t flashState = ANDROID_FLASH_STATE_UNAVAILABLE;
|
||||
UPDATE(md, ANDROID_FLASH_STATE, &flashState, 1);
|
||||
|
||||
// This means pipeline latency of X frame intervals. The maximum number is 4.
|
||||
const uint8_t requestPipelineMaxDepth = 4;
|
||||
UPDATE(md, ANDROID_REQUEST_PIPELINE_DEPTH, &requestPipelineMaxDepth, 1);
|
||||
|
||||
// android.scaler
|
||||
const int32_t crop_region[] = {
|
||||
activeArraySize.data.i32[0], activeArraySize.data.i32[1],
|
||||
activeArraySize.data.i32[2], activeArraySize.data.i32[3],
|
||||
};
|
||||
UPDATE(md, ANDROID_SCALER_CROP_REGION, crop_region, ARRAY_SIZE(crop_region));
|
||||
|
||||
// android.sensor
|
||||
UPDATE(md, ANDROID_SENSOR_TIMESTAMP, ×tamp, 1);
|
||||
|
||||
// android.statistics
|
||||
const uint8_t lensShadingMapMode = ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF;
|
||||
UPDATE(md, ANDROID_STATISTICS_LENS_SHADING_MAP_MODE, &lensShadingMapMode, 1);
|
||||
|
||||
const uint8_t sceneFlicker = ANDROID_STATISTICS_SCENE_FLICKER_NONE;
|
||||
UPDATE(md, ANDROID_STATISTICS_SCENE_FLICKER, &sceneFlicker, 1);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
#undef ARRAY_SIZE
|
||||
#undef UPDATE
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace V3_4
|
||||
|
||||
namespace V3_6 {
|
||||
namespace implementation {
|
||||
|
||||
AllocatedV4L2Frame::AllocatedV4L2Frame(sp<V3_4::implementation::V4L2Frame> frameIn) :
|
||||
Frame(frameIn->mWidth, frameIn->mHeight, frameIn->mFourcc) {
|
||||
uint8_t* dataIn;
|
||||
size_t dataSize;
|
||||
if (frameIn->getData(&dataIn, &dataSize) != 0) {
|
||||
ALOGE("%s: map input V4L2 frame failed!", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
mData.resize(dataSize);
|
||||
std::memcpy(mData.data(), dataIn, dataSize);
|
||||
}
|
||||
|
||||
int AllocatedV4L2Frame::getData(uint8_t** outData, size_t* dataSize) {
|
||||
if (outData == nullptr || dataSize == nullptr) {
|
||||
ALOGE("%s: outData(%p)/dataSize(%p) must not be null", __FUNCTION__, outData, dataSize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*outData = mData.data();
|
||||
*dataSize = mData.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
AllocatedV4L2Frame::~AllocatedV4L2Frame() {}
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace V3_6
|
||||
} // namespace device
|
||||
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H
|
||||
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H
|
||||
#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICESESSION_H
|
||||
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICESESSION_H
|
||||
|
||||
#include <android/hardware/camera/device/3.2/ICameraDevice.h>
|
||||
#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
|
||||
@@ -84,7 +84,8 @@ using ::android::sp;
|
||||
using ::android::Mutex;
|
||||
using ::android::base::unique_fd;
|
||||
|
||||
struct ExternalCameraDeviceSession : public virtual RefBase {
|
||||
struct ExternalCameraDeviceSession : public virtual RefBase,
|
||||
public virtual OutputThreadInterface {
|
||||
|
||||
ExternalCameraDeviceSession(const sp<ICameraDeviceCallback>&,
|
||||
const ExternalCameraConfig& cfg,
|
||||
@@ -110,6 +111,82 @@ struct ExternalCameraDeviceSession : public virtual RefBase {
|
||||
static const int kMaxStallStream = 1;
|
||||
static const uint32_t kMaxBytesPerPixel = 2;
|
||||
|
||||
class OutputThread : public android::Thread {
|
||||
public:
|
||||
OutputThread(wp<OutputThreadInterface> parent, CroppingType,
|
||||
const common::V1_0::helper::CameraMetadata&);
|
||||
virtual ~OutputThread();
|
||||
|
||||
Status allocateIntermediateBuffers(
|
||||
const Size& v4lSize, const Size& thumbSize,
|
||||
const hidl_vec<Stream>& streams,
|
||||
uint32_t blobBufferSize);
|
||||
Status submitRequest(const std::shared_ptr<HalRequest>&);
|
||||
void flush();
|
||||
void dump(int fd);
|
||||
virtual bool threadLoop() override;
|
||||
|
||||
void setExifMakeModel(const std::string& make, const std::string& model);
|
||||
|
||||
// The remaining request list is returned for offline processing
|
||||
std::list<std::shared_ptr<HalRequest>> switchToOffline();
|
||||
|
||||
protected:
|
||||
// Methods to request output buffer in parallel
|
||||
// No-op for device@3.4. Implemented in device@3.5
|
||||
virtual int requestBufferStart(const std::vector<HalStreamBuffer>&) { return 0; }
|
||||
virtual int waitForBufferRequestDone(
|
||||
/*out*/std::vector<HalStreamBuffer>*) { return 0; }
|
||||
|
||||
static const int kFlushWaitTimeoutSec = 3; // 3 sec
|
||||
static const int kReqWaitTimeoutMs = 33; // 33ms
|
||||
static const int kReqWaitTimesMax = 90; // 33ms * 90 ~= 3 sec
|
||||
|
||||
void waitForNextRequest(std::shared_ptr<HalRequest>* out);
|
||||
void signalRequestDone();
|
||||
|
||||
int cropAndScaleLocked(
|
||||
sp<AllocatedFrame>& in, const Size& outSize,
|
||||
YCbCrLayout* out);
|
||||
|
||||
int cropAndScaleThumbLocked(
|
||||
sp<AllocatedFrame>& in, const Size& outSize,
|
||||
YCbCrLayout* out);
|
||||
|
||||
int createJpegLocked(HalStreamBuffer &halBuf,
|
||||
const common::V1_0::helper::CameraMetadata& settings);
|
||||
|
||||
void clearIntermediateBuffers();
|
||||
|
||||
const wp<OutputThreadInterface> mParent;
|
||||
const CroppingType mCroppingType;
|
||||
const common::V1_0::helper::CameraMetadata mCameraCharacteristics;
|
||||
|
||||
mutable std::mutex mRequestListLock; // Protect acccess to mRequestList,
|
||||
// mProcessingRequest and mProcessingFrameNumer
|
||||
std::condition_variable mRequestCond; // signaled when a new request is submitted
|
||||
std::condition_variable mRequestDoneCond; // signaled when a request is done processing
|
||||
std::list<std::shared_ptr<HalRequest>> mRequestList;
|
||||
bool mProcessingRequest = false;
|
||||
uint32_t mProcessingFrameNumer = 0;
|
||||
|
||||
// V4L2 frameIn
|
||||
// (MJPG decode)-> mYu12Frame
|
||||
// (Scale)-> mScaledYu12Frames
|
||||
// (Format convert) -> output gralloc frames
|
||||
mutable std::mutex mBufferLock; // Protect access to intermediate buffers
|
||||
sp<AllocatedFrame> mYu12Frame;
|
||||
sp<AllocatedFrame> mYu12ThumbFrame;
|
||||
std::unordered_map<Size, sp<AllocatedFrame>, SizeHasher> mIntermediateBuffers;
|
||||
std::unordered_map<Size, sp<AllocatedFrame>, SizeHasher> mScaledYu12Frames;
|
||||
YCbCrLayout mYu12FrameLayout;
|
||||
YCbCrLayout mYu12ThumbFrameLayout;
|
||||
uint32_t mBlobBufferSize = 0; // 0 -> HAL derive buffer size, else: use given size
|
||||
|
||||
std::string mExifMake;
|
||||
std::string mExifModel;
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
// Methods from ::android::hardware::camera::device::V3_2::ICameraDeviceSession follow
|
||||
@@ -150,27 +227,22 @@ protected:
|
||||
ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb);
|
||||
|
||||
protected:
|
||||
struct HalStreamBuffer {
|
||||
int32_t streamId;
|
||||
uint64_t bufferId;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
PixelFormat format;
|
||||
V3_2::BufferUsageFlags usage;
|
||||
buffer_handle_t* bufPtr;
|
||||
int acquireFence;
|
||||
bool fenceTimeout;
|
||||
};
|
||||
// Methods from OutputThreadInterface
|
||||
virtual Status importBuffer(int32_t streamId,
|
||||
uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr,
|
||||
bool allowEmptyBuf) override;
|
||||
|
||||
struct HalRequest {
|
||||
uint32_t frameNumber;
|
||||
common::V1_0::helper::CameraMetadata setting;
|
||||
sp<V4L2Frame> frameIn;
|
||||
nsecs_t shutterTs;
|
||||
std::vector<HalStreamBuffer> buffers;
|
||||
};
|
||||
virtual Status processCaptureResult(std::shared_ptr<HalRequest>&) override;
|
||||
|
||||
static const uint64_t BUFFER_ID_NO_BUFFER = 0;
|
||||
virtual Status processCaptureRequestError(const std::shared_ptr<HalRequest>&,
|
||||
/*out*/std::vector<NotifyMsg>* msgs = nullptr,
|
||||
/*out*/std::vector<CaptureResult>* results = nullptr) override;
|
||||
|
||||
virtual ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const override;
|
||||
|
||||
virtual void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) override;
|
||||
// End of OutputThreadInterface methods
|
||||
|
||||
Status constructDefaultRequestSettingsRaw(RequestTemplate type,
|
||||
V3_2::CameraMetadata *outMetadata);
|
||||
@@ -219,11 +291,6 @@ protected:
|
||||
// Optional argument for ICameraDeviceSession@3.5 impl
|
||||
bool allowEmptyBuf = false);
|
||||
|
||||
Status importBuffer(int32_t streamId,
|
||||
uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr,
|
||||
bool allowEmptyBuf);
|
||||
|
||||
Status importBufferLocked(int32_t streamId,
|
||||
uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr,
|
||||
@@ -236,106 +303,15 @@ protected:
|
||||
|
||||
Status processOneCaptureRequest(const CaptureRequest& request);
|
||||
|
||||
Status processCaptureResult(std::shared_ptr<HalRequest>&);
|
||||
Status processCaptureRequestError(const std::shared_ptr<HalRequest>&);
|
||||
void notifyShutter(uint32_t frameNumber, nsecs_t shutterTs);
|
||||
void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec);
|
||||
void invokeProcessCaptureResultCallback(
|
||||
hidl_vec<CaptureResult> &results, bool tryWriteFmq);
|
||||
static void freeReleaseFences(hidl_vec<CaptureResult>&);
|
||||
|
||||
Size getMaxJpegResolution() const;
|
||||
Size getMaxThumbResolution() const;
|
||||
|
||||
ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const;
|
||||
|
||||
int waitForV4L2BufferReturnLocked(std::unique_lock<std::mutex>& lk);
|
||||
|
||||
class OutputThread : public android::Thread {
|
||||
public:
|
||||
OutputThread(wp<ExternalCameraDeviceSession> parent, CroppingType);
|
||||
virtual ~OutputThread();
|
||||
|
||||
Status allocateIntermediateBuffers(
|
||||
const Size& v4lSize, const Size& thumbSize,
|
||||
const hidl_vec<Stream>& streams,
|
||||
uint32_t blobBufferSize);
|
||||
Status submitRequest(const std::shared_ptr<HalRequest>&);
|
||||
void flush();
|
||||
void dump(int fd);
|
||||
virtual bool threadLoop() override;
|
||||
|
||||
void setExifMakeModel(const std::string& make, const std::string& model);
|
||||
|
||||
protected:
|
||||
// Methods to request output buffer in parallel
|
||||
// No-op for device@3.4. Implemented in device@3.5
|
||||
virtual int requestBufferStart(const std::vector<HalStreamBuffer>&) { return 0; }
|
||||
virtual int waitForBufferRequestDone(
|
||||
/*out*/std::vector<HalStreamBuffer>*) { return 0; }
|
||||
|
||||
static const uint32_t FLEX_YUV_GENERIC = static_cast<uint32_t>('F') |
|
||||
static_cast<uint32_t>('L') << 8 | static_cast<uint32_t>('E') << 16 |
|
||||
static_cast<uint32_t>('X') << 24;
|
||||
// returns FLEX_YUV_GENERIC for formats other than YV12/YU12/NV12/NV21
|
||||
static uint32_t getFourCcFromLayout(const YCbCrLayout&);
|
||||
static int getCropRect(
|
||||
CroppingType ct, const Size& inSize, const Size& outSize, IMapper::Rect* out);
|
||||
|
||||
static const int kFlushWaitTimeoutSec = 3; // 3 sec
|
||||
static const int kReqWaitTimeoutMs = 33; // 33ms
|
||||
static const int kReqWaitTimesMax = 90; // 33ms * 90 ~= 3 sec
|
||||
|
||||
void waitForNextRequest(std::shared_ptr<HalRequest>* out);
|
||||
void signalRequestDone();
|
||||
|
||||
int cropAndScaleLocked(
|
||||
sp<AllocatedFrame>& in, const Size& outSize,
|
||||
YCbCrLayout* out);
|
||||
|
||||
int cropAndScaleThumbLocked(
|
||||
sp<AllocatedFrame>& in, const Size& outSize,
|
||||
YCbCrLayout* out);
|
||||
|
||||
int formatConvertLocked(const YCbCrLayout& in, const YCbCrLayout& out,
|
||||
Size sz, uint32_t format);
|
||||
|
||||
static int encodeJpegYU12(const Size &inSz,
|
||||
const YCbCrLayout& inLayout, int jpegQuality,
|
||||
const void *app1Buffer, size_t app1Size,
|
||||
void *out, size_t maxOutSize,
|
||||
size_t &actualCodeSize);
|
||||
|
||||
int createJpegLocked(HalStreamBuffer &halBuf, const std::shared_ptr<HalRequest>& req);
|
||||
|
||||
const wp<ExternalCameraDeviceSession> mParent;
|
||||
const CroppingType mCroppingType;
|
||||
|
||||
mutable std::mutex mRequestListLock; // Protect acccess to mRequestList,
|
||||
// mProcessingRequest and mProcessingFrameNumer
|
||||
std::condition_variable mRequestCond; // signaled when a new request is submitted
|
||||
std::condition_variable mRequestDoneCond; // signaled when a request is done processing
|
||||
std::list<std::shared_ptr<HalRequest>> mRequestList;
|
||||
bool mProcessingRequest = false;
|
||||
uint32_t mProcessingFrameNumer = 0;
|
||||
|
||||
// V4L2 frameIn
|
||||
// (MJPG decode)-> mYu12Frame
|
||||
// (Scale)-> mScaledYu12Frames
|
||||
// (Format convert) -> output gralloc frames
|
||||
mutable std::mutex mBufferLock; // Protect access to intermediate buffers
|
||||
sp<AllocatedFrame> mYu12Frame;
|
||||
sp<AllocatedFrame> mYu12ThumbFrame;
|
||||
std::unordered_map<Size, sp<AllocatedFrame>, SizeHasher> mIntermediateBuffers;
|
||||
std::unordered_map<Size, sp<AllocatedFrame>, SizeHasher> mScaledYu12Frames;
|
||||
YCbCrLayout mYu12FrameLayout;
|
||||
YCbCrLayout mYu12ThumbFrameLayout;
|
||||
uint32_t mBlobBufferSize = 0; // 0 -> HAL derive buffer size, else: use given size
|
||||
|
||||
std::string mExifMake;
|
||||
std::string mExifModel;
|
||||
};
|
||||
|
||||
// Protect (most of) HIDL interface methods from synchronized-entering
|
||||
mutable Mutex mInterfaceLock;
|
||||
|
||||
@@ -381,12 +357,6 @@ protected:
|
||||
std::mutex mInflightFramesLock; // protect mInflightFrames
|
||||
std::unordered_set<uint32_t> mInflightFrames;
|
||||
|
||||
// buffers currently circulating between HAL and camera service
|
||||
// key: bufferId sent via HIDL interface
|
||||
// value: imported buffer_handle_t
|
||||
// Buffer will be imported during processCaptureRequest and will be freed
|
||||
// when the its stream is deleted or camera device session is closed
|
||||
typedef std::unordered_map<uint64_t, buffer_handle_t> CirculatingBuffers;
|
||||
// Stream ID -> circulating buffers map
|
||||
std::map<int, CirculatingBuffers> mCirculatingBuffers;
|
||||
// Protect mCirculatingBuffers, must not lock mLock after acquiring this lock
|
||||
@@ -395,6 +365,8 @@ protected:
|
||||
std::mutex mAfTriggerLock; // protect mAfTrigger
|
||||
bool mAfTrigger = false;
|
||||
|
||||
uint32_t mBlobBufferSize = 0;
|
||||
|
||||
static HandleImporter sHandleImporter;
|
||||
|
||||
/* Beginning of members not changed after initialize() */
|
||||
@@ -410,6 +382,9 @@ protected:
|
||||
|
||||
const Size mMaxThumbResolution;
|
||||
const Size mMaxJpegResolution;
|
||||
|
||||
std::string mExifMake;
|
||||
std::string mExifModel;
|
||||
/* End of members not changed after initialize() */
|
||||
|
||||
private:
|
||||
@@ -484,4 +459,4 @@ private:
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H
|
||||
#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICESESSION_H
|
||||
|
||||
@@ -17,16 +17,27 @@
|
||||
#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMUTIL_H
|
||||
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMUTIL_H
|
||||
|
||||
#include <android/hardware/camera/common/1.0/types.h>
|
||||
#include <android/hardware/camera/device/3.2/types.h>
|
||||
#include <android/hardware/graphics/common/1.0/types.h>
|
||||
#include <android/hardware/graphics/mapper/2.0/IMapper.h>
|
||||
#include <inttypes.h>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include "tinyxml2.h" // XML parsing
|
||||
#include "utils/LightRefBase.h"
|
||||
#include "utils/Timers.h"
|
||||
#include <CameraMetadata.h>
|
||||
#include <HandleImporter.h>
|
||||
|
||||
using android::hardware::graphics::mapper::V2_0::IMapper;
|
||||
using android::hardware::graphics::mapper::V2_0::YCbCrLayout;
|
||||
|
||||
using ::android::hardware::graphics::mapper::V2_0::IMapper;
|
||||
using ::android::hardware::graphics::mapper::V2_0::YCbCrLayout;
|
||||
using ::android::hardware::camera::common::V1_0::helper::HandleImporter;
|
||||
using ::android::hardware::camera::common::V1_0::Status;
|
||||
using ::android::hardware::camera::device::V3_2::ErrorCode;
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -113,16 +124,28 @@ struct SupportedV4L2Format {
|
||||
std::vector<FrameRate> frameRates;
|
||||
};
|
||||
|
||||
// A Base class with basic information about a frame
|
||||
struct Frame : public VirtualLightRefBase {
|
||||
public:
|
||||
Frame(uint32_t width, uint32_t height, uint32_t fourcc);
|
||||
const uint32_t mWidth;
|
||||
const uint32_t mHeight;
|
||||
const uint32_t mFourcc;
|
||||
|
||||
// getData might involve map/allocation
|
||||
virtual int getData(uint8_t** outData, size_t* dataSize) = 0;
|
||||
};
|
||||
|
||||
// A class provide access to a dequeued V4L2 frame buffer (mostly in MJPG format)
|
||||
// Also contains necessary information to enqueue the buffer back to V4L2 buffer queue
|
||||
class V4L2Frame : public virtual VirtualLightRefBase {
|
||||
class V4L2Frame : public Frame {
|
||||
public:
|
||||
V4L2Frame(uint32_t w, uint32_t h, uint32_t fourcc, int bufIdx, int fd,
|
||||
uint32_t dataSize, uint64_t offset);
|
||||
~V4L2Frame() override;
|
||||
const uint32_t mWidth;
|
||||
const uint32_t mHeight;
|
||||
const uint32_t mFourcc;
|
||||
|
||||
virtual int getData(uint8_t** outData, size_t* dataSize) override;
|
||||
|
||||
const int mBufferIndex; // for later enqueue
|
||||
int map(uint8_t** data, size_t* dataSize);
|
||||
int unmap();
|
||||
@@ -137,13 +160,13 @@ private:
|
||||
|
||||
// A RAII class representing a CPU allocated YUV frame used as intermeidate buffers
|
||||
// when generating output images.
|
||||
class AllocatedFrame : public virtual VirtualLightRefBase {
|
||||
class AllocatedFrame : public Frame {
|
||||
public:
|
||||
AllocatedFrame(uint32_t w, uint32_t h); // TODO: use Size?
|
||||
AllocatedFrame(uint32_t w, uint32_t h); // only support V4L2_PIX_FMT_YUV420 for now
|
||||
~AllocatedFrame() override;
|
||||
const uint32_t mWidth;
|
||||
const uint32_t mHeight;
|
||||
const uint32_t mFourcc; // Only support YU12 format for now
|
||||
|
||||
virtual int getData(uint8_t** outData, size_t* dataSize) override;
|
||||
|
||||
int allocate(YCbCrLayout* out = nullptr);
|
||||
int getLayout(YCbCrLayout* out);
|
||||
int getCroppedLayout(const IMapper::Rect&, YCbCrLayout* out); // return non-zero for bad input
|
||||
@@ -165,8 +188,110 @@ const float kMinAspectRatio = 1.f;
|
||||
|
||||
bool isAspectRatioClose(float ar1, float ar2);
|
||||
|
||||
struct HalStreamBuffer {
|
||||
int32_t streamId;
|
||||
uint64_t bufferId;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
::android::hardware::graphics::common::V1_0::PixelFormat format;
|
||||
::android::hardware::camera::device::V3_2::BufferUsageFlags usage;
|
||||
buffer_handle_t* bufPtr;
|
||||
int acquireFence;
|
||||
bool fenceTimeout;
|
||||
};
|
||||
|
||||
struct HalRequest {
|
||||
uint32_t frameNumber;
|
||||
common::V1_0::helper::CameraMetadata setting;
|
||||
sp<Frame> frameIn;
|
||||
nsecs_t shutterTs;
|
||||
std::vector<HalStreamBuffer> buffers;
|
||||
};
|
||||
|
||||
static const uint64_t BUFFER_ID_NO_BUFFER = 0;
|
||||
|
||||
// buffers currently circulating between HAL and camera service
|
||||
// key: bufferId sent via HIDL interface
|
||||
// value: imported buffer_handle_t
|
||||
// Buffer will be imported during processCaptureRequest (or requestStreamBuffer
|
||||
// in the case of HAL buffer manager is enabled) and will be freed
|
||||
// when the stream is deleted or camera device session is closed
|
||||
typedef std::unordered_map<uint64_t, buffer_handle_t> CirculatingBuffers;
|
||||
|
||||
::android::hardware::camera::common::V1_0::Status importBufferImpl(
|
||||
/*inout*/std::map<int, CirculatingBuffers>& circulatingBuffers,
|
||||
/*inout*/HandleImporter& handleImporter,
|
||||
int32_t streamId,
|
||||
uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr,
|
||||
bool allowEmptyBuf);
|
||||
|
||||
static const uint32_t FLEX_YUV_GENERIC = static_cast<uint32_t>('F') |
|
||||
static_cast<uint32_t>('L') << 8 | static_cast<uint32_t>('E') << 16 |
|
||||
static_cast<uint32_t>('X') << 24;
|
||||
|
||||
// returns FLEX_YUV_GENERIC for formats other than YV12/YU12/NV12/NV21
|
||||
uint32_t getFourCcFromLayout(const YCbCrLayout&);
|
||||
|
||||
using ::android::hardware::camera::external::common::Size;
|
||||
int getCropRect(CroppingType ct, const Size& inSize,
|
||||
const Size& outSize, IMapper::Rect* out);
|
||||
|
||||
int formatConvert(const YCbCrLayout& in, const YCbCrLayout& out, Size sz, uint32_t format);
|
||||
|
||||
int encodeJpegYU12(const Size &inSz,
|
||||
const YCbCrLayout& inLayout, int jpegQuality,
|
||||
const void *app1Buffer, size_t app1Size,
|
||||
void *out, size_t maxOutSize,
|
||||
size_t &actualCodeSize);
|
||||
|
||||
Size getMaxThumbnailResolution(const common::V1_0::helper::CameraMetadata&);
|
||||
|
||||
void freeReleaseFences(hidl_vec<V3_2::CaptureResult>&);
|
||||
|
||||
status_t fillCaptureResultCommon(common::V1_0::helper::CameraMetadata& md, nsecs_t timestamp,
|
||||
camera_metadata_ro_entry& activeArraySize);
|
||||
|
||||
// Interface for OutputThread calling back to parent
|
||||
struct OutputThreadInterface : public virtual RefBase {
|
||||
virtual ::android::hardware::camera::common::V1_0::Status importBuffer(
|
||||
int32_t streamId, uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr, bool allowEmptyBuf) = 0;
|
||||
|
||||
virtual void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) = 0;
|
||||
|
||||
// Callbacks are fired within the method if msgs/results are nullptr.
|
||||
// Otherwise the callbacks will be returned and caller is responsible to
|
||||
// fire the callback later
|
||||
virtual ::android::hardware::camera::common::V1_0::Status processCaptureRequestError(
|
||||
const std::shared_ptr<HalRequest>&,
|
||||
/*out*/std::vector<V3_2::NotifyMsg>* msgs = nullptr,
|
||||
/*out*/std::vector<V3_2::CaptureResult>* results = nullptr) = 0;
|
||||
|
||||
virtual ::android::hardware::camera::common::V1_0::Status processCaptureResult(
|
||||
std::shared_ptr<HalRequest>&) = 0;
|
||||
|
||||
virtual ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const = 0;
|
||||
};
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace V3_4
|
||||
|
||||
namespace V3_6 {
|
||||
namespace implementation {
|
||||
|
||||
// A CPU copy of a mapped V4L2Frame. Will map the input V4L2 frame.
|
||||
class AllocatedV4L2Frame : public V3_4::implementation::Frame {
|
||||
public:
|
||||
AllocatedV4L2Frame(sp<V3_4::implementation::V4L2Frame> frameIn);
|
||||
~AllocatedV4L2Frame() override;
|
||||
virtual int getData(uint8_t** outData, size_t* dataSize) override;
|
||||
private:
|
||||
std::vector<uint8_t> mData;
|
||||
};
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace V3_6
|
||||
} // namespace device
|
||||
} // namespace camera
|
||||
} // namespace hardware
|
||||
|
||||
@@ -80,7 +80,7 @@ Status ExternalCameraDeviceSession::importRequestLocked(
|
||||
|
||||
|
||||
ExternalCameraDeviceSession::BufferRequestThread::BufferRequestThread(
|
||||
wp<ExternalCameraDeviceSession> parent,
|
||||
wp<OutputThreadInterface> parent,
|
||||
sp<V3_5::ICameraDeviceCallback> callbacks) :
|
||||
mParent(parent),
|
||||
mCallbacks(callbacks) {}
|
||||
@@ -254,7 +254,8 @@ void ExternalCameraDeviceSession::initOutputThread() {
|
||||
mBufferRequestThread = new BufferRequestThread(this, mCallback_3_5);
|
||||
mBufferRequestThread->run("ExtCamBufReq", PRIORITY_DISPLAY);
|
||||
}
|
||||
mOutputThread = new OutputThread(this, mCroppingType, mBufferRequestThread);
|
||||
mOutputThread = new OutputThread(
|
||||
this, mCroppingType, mCameraCharacteristics, mBufferRequestThread);
|
||||
}
|
||||
|
||||
void ExternalCameraDeviceSession::closeOutputThreadImpl() {
|
||||
@@ -271,10 +272,11 @@ void ExternalCameraDeviceSession::closeOutputThread() {
|
||||
}
|
||||
|
||||
ExternalCameraDeviceSession::OutputThread::OutputThread(
|
||||
wp<ExternalCameraDeviceSession> parent,
|
||||
wp<OutputThreadInterface> parent,
|
||||
CroppingType ct,
|
||||
const common::V1_0::helper::CameraMetadata& chars,
|
||||
sp<BufferRequestThread> bufReqThread) :
|
||||
V3_4::implementation::ExternalCameraDeviceSession::OutputThread(parent, ct),
|
||||
V3_4::implementation::ExternalCameraDeviceSession::OutputThread(parent, ct, chars),
|
||||
mBufferRequestThread(bufReqThread) {}
|
||||
|
||||
ExternalCameraDeviceSession::OutputThread::~OutputThread() {}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICE3SESSION_H
|
||||
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICE3SESSION_H
|
||||
#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICESESSION_H
|
||||
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICESESSION_H
|
||||
|
||||
#include <android/hardware/camera/device/3.5/ICameraDeviceCallback.h>
|
||||
#include <android/hardware/camera/device/3.5/ICameraDeviceSession.h>
|
||||
@@ -72,6 +72,7 @@ using ::android::base::unique_fd;
|
||||
|
||||
using ::android::hardware::camera::device::V3_4::implementation::SupportedV4L2Format;
|
||||
using ::android::hardware::camera::device::V3_4::implementation::CroppingType;
|
||||
using ::android::hardware::camera::device::V3_4::implementation::HalStreamBuffer;
|
||||
|
||||
struct ExternalCameraDeviceSession : public V3_4::implementation::ExternalCameraDeviceSession {
|
||||
|
||||
@@ -97,6 +98,62 @@ struct ExternalCameraDeviceSession : public V3_4::implementation::ExternalCamera
|
||||
config, supportedFormats, devCfg);
|
||||
}
|
||||
|
||||
class BufferRequestThread : public android::Thread {
|
||||
public:
|
||||
BufferRequestThread(
|
||||
wp<OutputThreadInterface> parent,
|
||||
sp<V3_5::ICameraDeviceCallback> callbacks);
|
||||
|
||||
int requestBufferStart(const std::vector<HalStreamBuffer>&);
|
||||
int waitForBufferRequestDone(
|
||||
/*out*/std::vector<HalStreamBuffer>*);
|
||||
|
||||
virtual bool threadLoop() override;
|
||||
|
||||
private:
|
||||
void waitForNextRequest();
|
||||
|
||||
const wp<OutputThreadInterface> mParent;
|
||||
const sp<V3_5::ICameraDeviceCallback> mCallbacks;
|
||||
|
||||
std::mutex mLock;
|
||||
bool mRequestingBuffer = false;
|
||||
|
||||
std::vector<HalStreamBuffer> mBufferReqs;
|
||||
std::vector<HalStreamBuffer> mPendingReturnBufferReqs;
|
||||
// mHalBufferReqs is not under mLock protection during the HIDL transaction
|
||||
hidl_vec<BufferRequest> mHalBufferReqs;
|
||||
|
||||
// request buffers takes much less time in steady state, but can take much longer
|
||||
// when requesting 1st buffer from a stream.
|
||||
// TODO: consider a separate timeout for new vs. steady state?
|
||||
// TODO: or make sure framework is warming up the pipeline during configure new stream?
|
||||
static const int kReqProcTimeoutMs = 66;
|
||||
|
||||
static const int kReqWaitTimeoutMs = 33;
|
||||
static const int kReqWaitTimesWarn = 90; // 33ms * 90 ~= 3 sec
|
||||
std::condition_variable mRequestCond; // signaled when a new buffer request incoming
|
||||
std::condition_variable mRequestDoneCond; // signaled when a request is done
|
||||
};
|
||||
|
||||
class OutputThread :
|
||||
public V3_4::implementation::ExternalCameraDeviceSession::OutputThread {
|
||||
public:
|
||||
// TODO: pass buffer request thread to OutputThread ctor
|
||||
OutputThread(wp<OutputThreadInterface> parent, CroppingType,
|
||||
const common::V1_0::helper::CameraMetadata&,
|
||||
sp<BufferRequestThread> bufReqThread);
|
||||
virtual ~OutputThread();
|
||||
|
||||
protected:
|
||||
// Methods to request output buffer in parallel
|
||||
virtual int requestBufferStart(const std::vector<HalStreamBuffer>&) override;
|
||||
virtual int waitForBufferRequestDone(
|
||||
/*out*/std::vector<HalStreamBuffer>*) override;
|
||||
|
||||
const sp<BufferRequestThread> mBufferRequestThread;
|
||||
};
|
||||
|
||||
protected:
|
||||
// Methods from v3.4 and earlier will trampoline to inherited implementation
|
||||
Return<void> configureStreams_3_5(
|
||||
@@ -120,63 +177,8 @@ protected:
|
||||
hidl_vec<buffer_handle_t*>& allBufPtrs,
|
||||
hidl_vec<int>& allFences) override;
|
||||
|
||||
class BufferRequestThread : public android::Thread {
|
||||
public:
|
||||
BufferRequestThread(
|
||||
wp<ExternalCameraDeviceSession> parent,
|
||||
sp<V3_5::ICameraDeviceCallback> callbacks);
|
||||
|
||||
int requestBufferStart(const std::vector<HalStreamBuffer>&);
|
||||
int waitForBufferRequestDone(
|
||||
/*out*/std::vector<HalStreamBuffer>*);
|
||||
|
||||
virtual bool threadLoop() override;
|
||||
|
||||
private:
|
||||
void waitForNextRequest();
|
||||
|
||||
const wp<ExternalCameraDeviceSession> mParent;
|
||||
const sp<V3_5::ICameraDeviceCallback> mCallbacks;
|
||||
|
||||
std::mutex mLock;
|
||||
bool mRequestingBuffer = false;
|
||||
|
||||
std::vector<HalStreamBuffer> mBufferReqs;
|
||||
std::vector<HalStreamBuffer> mPendingReturnBufferReqs;
|
||||
// mHalBufferReqs is not under mLock protection during the HIDL transaction
|
||||
hidl_vec<BufferRequest> mHalBufferReqs;
|
||||
|
||||
// request buffers takes much less time in steady state, but can take much longer
|
||||
// when requesting 1st buffer from a stream.
|
||||
// TODO: consider a separate timeout for new vs. steady state?
|
||||
// TODO: or make sure framework is warming up the pipeline during configure new stream?
|
||||
static const int kReqProcTimeoutMs = 66;
|
||||
|
||||
static const int kReqWaitTimeoutMs = 33;
|
||||
static const int kReqWaitTimesWarn = 90; // 33ms * 90 ~= 3 sec
|
||||
std::condition_variable mRequestCond; // signaled when a new buffer request incoming
|
||||
std::condition_variable mRequestDoneCond; // signaled when a request is done
|
||||
};
|
||||
|
||||
sp<BufferRequestThread> mBufferRequestThread;
|
||||
|
||||
class OutputThread :
|
||||
public V3_4::implementation::ExternalCameraDeviceSession::OutputThread {
|
||||
public:
|
||||
// TODO: pass buffer request thread to OutputThread ctor
|
||||
OutputThread(wp<ExternalCameraDeviceSession> parent, CroppingType,
|
||||
sp<BufferRequestThread> bufReqThread);
|
||||
virtual ~OutputThread();
|
||||
|
||||
protected:
|
||||
// Methods to request output buffer in parallel
|
||||
virtual int requestBufferStart(const std::vector<HalStreamBuffer>&) override;
|
||||
virtual int waitForBufferRequestDone(
|
||||
/*out*/std::vector<HalStreamBuffer>*) override;
|
||||
|
||||
const sp<BufferRequestThread> mBufferRequestThread;
|
||||
};
|
||||
|
||||
sp<V3_5::ICameraDeviceCallback> mCallback_3_5;
|
||||
bool mSupportBufMgr;
|
||||
|
||||
@@ -270,4 +272,4 @@ private:
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICE3SESSION_H
|
||||
#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICESESSION_H
|
||||
|
||||
@@ -28,6 +28,7 @@ cc_library_shared {
|
||||
srcs: [
|
||||
"ExternalCameraDevice.cpp",
|
||||
"ExternalCameraDeviceSession.cpp",
|
||||
"ExternalCameraOfflineSession.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"libhidlbase",
|
||||
|
||||
@@ -73,12 +73,8 @@ Return<void> ExternalCameraDeviceSession::configureStreams_3_6(
|
||||
|
||||
Status status = configureStreams(config_v32, &outStreams_v33, blobBufferSize);
|
||||
|
||||
outStreams.streams.resize(outStreams_v33.streams.size());
|
||||
for (size_t i = 0; i < outStreams.streams.size(); i++) {
|
||||
outStreams.streams[i].v3_4.v3_3 = outStreams_v33.streams[i];
|
||||
// TODO: implement it later
|
||||
outStreams.streams[i].supportOffline = false;
|
||||
}
|
||||
fillOutputStream3_6(outStreams_v33, &outStreams);
|
||||
|
||||
_hidl_cb(status, outStreams);
|
||||
return Void();
|
||||
}
|
||||
@@ -86,12 +82,273 @@ Return<void> ExternalCameraDeviceSession::configureStreams_3_6(
|
||||
Return<void> ExternalCameraDeviceSession::switchToOffline(
|
||||
const hidl_vec<int32_t>& streamsToKeep,
|
||||
ICameraDeviceSession::switchToOffline_cb _hidl_cb) {
|
||||
// TODO: implement this
|
||||
(void) streamsToKeep;
|
||||
(void) _hidl_cb;
|
||||
std::vector<NotifyMsg> msgs;
|
||||
std::vector<CaptureResult> results;
|
||||
CameraOfflineSessionInfo info;
|
||||
sp<ICameraOfflineSession> session;
|
||||
|
||||
Status st = switchToOffline(streamsToKeep, &msgs, &results, &info, &session);
|
||||
|
||||
mCallback->notify(msgs);
|
||||
hidl_vec<CaptureResult> hidlResults(std::move(results));
|
||||
invokeProcessCaptureResultCallback(hidlResults, /* tryWriteFmq */true);
|
||||
V3_4::implementation::freeReleaseFences(hidlResults);
|
||||
|
||||
_hidl_cb(st, info, session);
|
||||
return Void();
|
||||
}
|
||||
|
||||
void ExternalCameraDeviceSession::fillOutputStream3_6(
|
||||
const V3_3::HalStreamConfiguration& outStreams_v33,
|
||||
/*out*/V3_6::HalStreamConfiguration* outStreams_v36) {
|
||||
if (outStreams_v36 == nullptr) {
|
||||
ALOGE("%s: outStreams_v36 must not be null!", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
Mutex::Autolock _l(mLock);
|
||||
outStreams_v36->streams.resize(outStreams_v33.streams.size());
|
||||
for (size_t i = 0; i < outStreams_v36->streams.size(); i++) {
|
||||
outStreams_v36->streams[i].v3_4.v3_3 = outStreams_v33.streams[i];
|
||||
outStreams_v36->streams[i].supportOffline =
|
||||
supportOfflineLocked(outStreams_v33.streams[i].v3_2.id);
|
||||
}
|
||||
}
|
||||
|
||||
bool ExternalCameraDeviceSession::supportOfflineLocked(int32_t streamId) {
|
||||
const Stream& stream = mStreamMap[streamId];
|
||||
if (stream.format == PixelFormat::BLOB &&
|
||||
stream.dataSpace == static_cast<int32_t>(Dataspace::V0_JFIF)) {
|
||||
return true;
|
||||
}
|
||||
// TODO: support YUV output stream?
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExternalCameraDeviceSession::canDropRequest(const hidl_vec<int32_t>& offlineStreams,
|
||||
std::shared_ptr<V3_4::implementation::HalRequest> halReq) {
|
||||
for (const auto& buffer : halReq->buffers) {
|
||||
for (auto offlineStreamId : offlineStreams) {
|
||||
if (buffer.streamId == offlineStreamId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only drop a request completely if it has no offline output
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExternalCameraDeviceSession::fillOfflineSessionInfo(const hidl_vec<int32_t>& offlineStreams,
|
||||
std::deque<std::shared_ptr<HalRequest>>& offlineReqs,
|
||||
const std::map<int, CirculatingBuffers>& circulatingBuffers,
|
||||
/*out*/CameraOfflineSessionInfo* info) {
|
||||
if (info == nullptr) {
|
||||
ALOGE("%s: output info must not be null!", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
info->offlineStreams.resize(offlineStreams.size());
|
||||
info->offlineRequests.resize(offlineReqs.size());
|
||||
|
||||
std::unordered_map<int32_t, uint32_t> outstandingBufs(offlineStreams.size());
|
||||
for (const auto streamId : offlineStreams) {
|
||||
outstandingBufs.insert({streamId, 0});
|
||||
}
|
||||
// Fill in offline reqs and count outstanding buffers
|
||||
for (size_t i = 0; i < offlineReqs.size(); i++) {
|
||||
info->offlineRequests[i].frameNumber = offlineReqs[i]->frameNumber;
|
||||
info->offlineRequests[i].pendingStreams.resize(offlineReqs[i]->buffers.size());
|
||||
for (size_t bIdx = 0; bIdx < offlineReqs[i]->buffers.size(); bIdx++) {
|
||||
int32_t streamId = offlineReqs[i]->buffers[bIdx].streamId;
|
||||
info->offlineRequests[i].pendingStreams[bIdx] = streamId;
|
||||
outstandingBufs[streamId]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < offlineStreams.size(); i++) {
|
||||
int32_t streamId = offlineStreams[i];
|
||||
info->offlineStreams[i].id = streamId;
|
||||
info->offlineStreams[i].numOutstandingBuffers = outstandingBufs[streamId];
|
||||
const CirculatingBuffers& bufIdMap = circulatingBuffers.at(streamId);
|
||||
info->offlineStreams[i].circulatingBufferIds.resize(bufIdMap.size());
|
||||
size_t bIdx = 0;
|
||||
for (const auto& pair : bufIdMap) {
|
||||
// Fill in bufferId
|
||||
info->offlineStreams[i].circulatingBufferIds[bIdx++] = pair.first;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Status ExternalCameraDeviceSession::switchToOffline(const hidl_vec<int32_t>& offlineStreams,
|
||||
/*out*/std::vector<NotifyMsg>* msgs,
|
||||
/*out*/std::vector<CaptureResult>* results,
|
||||
/*out*/CameraOfflineSessionInfo* info,
|
||||
/*out*/sp<ICameraOfflineSession>* session) {
|
||||
ATRACE_CALL();
|
||||
if (offlineStreams.size() > 1) {
|
||||
ALOGE("%s: more than one offline stream is not supported", __FUNCTION__);
|
||||
return Status::ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
if (msgs == nullptr || results == nullptr || info == nullptr || session == nullptr) {
|
||||
ALOGE("%s: output arguments (%p, %p, %p, %p) must not be null", __FUNCTION__,
|
||||
msgs, results, info, session);
|
||||
return Status::ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
msgs->clear();
|
||||
results->clear();
|
||||
|
||||
Mutex::Autolock _il(mInterfaceLock);
|
||||
Status status = initStatus();
|
||||
if (status != Status::OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
Mutex::Autolock _l(mLock);
|
||||
for (auto streamId : offlineStreams) {
|
||||
if (!supportOfflineLocked(streamId)) {
|
||||
return Status::ILLEGAL_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
// pause output thread and get all remaining inflight requests
|
||||
auto remainingReqs = mOutputThread->switchToOffline();
|
||||
std::vector<std::shared_ptr<V3_4::implementation::HalRequest>> halReqs;
|
||||
|
||||
// Send out buffer/request error for remaining requests and filter requests
|
||||
// to be handled in offline mode
|
||||
for (auto& halReq : remainingReqs) {
|
||||
bool dropReq = canDropRequest(offlineStreams, halReq);
|
||||
if (dropReq) {
|
||||
// Request is dropped completely. Just send request error and
|
||||
// there is no need to send the request to offline session
|
||||
processCaptureRequestError(halReq, msgs, results);
|
||||
continue;
|
||||
}
|
||||
|
||||
// All requests reach here must have at least one offline stream output
|
||||
NotifyMsg shutter;
|
||||
shutter.type = MsgType::SHUTTER;
|
||||
shutter.msg.shutter.frameNumber = halReq->frameNumber;
|
||||
shutter.msg.shutter.timestamp = halReq->shutterTs;
|
||||
msgs->push_back(shutter);
|
||||
|
||||
std::vector<V3_4::implementation::HalStreamBuffer> offlineBuffers;
|
||||
for (const auto& buffer : halReq->buffers) {
|
||||
bool dropBuffer = true;
|
||||
for (auto offlineStreamId : offlineStreams) {
|
||||
if (buffer.streamId == offlineStreamId) {
|
||||
dropBuffer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dropBuffer) {
|
||||
NotifyMsg error;
|
||||
error.type = MsgType::ERROR;
|
||||
error.msg.error.frameNumber = halReq->frameNumber;
|
||||
error.msg.error.errorStreamId = buffer.streamId;
|
||||
error.msg.error.errorCode = ErrorCode::ERROR_BUFFER;
|
||||
msgs->push_back(error);
|
||||
|
||||
CaptureResult result;
|
||||
result.frameNumber = halReq->frameNumber;
|
||||
result.partialResult = 0; // buffer only result
|
||||
result.inputBuffer.streamId = -1;
|
||||
result.outputBuffers.resize(1);
|
||||
result.outputBuffers[0].streamId = buffer.streamId;
|
||||
result.outputBuffers[0].bufferId = buffer.bufferId;
|
||||
result.outputBuffers[0].status = BufferStatus::ERROR;
|
||||
if (buffer.acquireFence >= 0) {
|
||||
native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
|
||||
handle->data[0] = buffer.acquireFence;
|
||||
result.outputBuffers[0].releaseFence.setTo(handle, /*shouldOwn*/false);
|
||||
}
|
||||
results->push_back(result);
|
||||
} else {
|
||||
offlineBuffers.push_back(buffer);
|
||||
}
|
||||
}
|
||||
halReq->buffers = offlineBuffers;
|
||||
halReqs.push_back(halReq);
|
||||
}
|
||||
|
||||
// convert hal requests to offline request
|
||||
std::deque<std::shared_ptr<HalRequest>> offlineReqs(halReqs.size());
|
||||
for (auto& v4lReq : halReqs) {
|
||||
std::shared_ptr<HalRequest> halReq = std::make_shared<HalRequest>();
|
||||
halReq->frameNumber = v4lReq->frameNumber;
|
||||
halReq->setting = v4lReq->setting;
|
||||
halReq->shutterTs = v4lReq->shutterTs;
|
||||
halReq->buffers = v4lReq->buffers;
|
||||
sp<V3_4::implementation::V4L2Frame> v4l2Frame =
|
||||
static_cast<V3_4::implementation::V4L2Frame*>(v4lReq->frameIn.get());
|
||||
halReq->frameIn = new AllocatedV4L2Frame(v4l2Frame);
|
||||
offlineReqs.push_back(halReq);
|
||||
// enqueue V4L2 frame
|
||||
enqueueV4l2Frame(v4l2Frame);
|
||||
}
|
||||
|
||||
// Collect buffer caches/streams
|
||||
hidl_vec<Stream> streamInfos;
|
||||
streamInfos.resize(offlineStreams.size());
|
||||
std::map<int, CirculatingBuffers> circulatingBuffers;
|
||||
{
|
||||
Mutex::Autolock _l(mCbsLock);
|
||||
size_t idx = 0;
|
||||
for(auto streamId : offlineStreams) {
|
||||
circulatingBuffers[streamId] = mCirculatingBuffers.at(streamId);
|
||||
mCirculatingBuffers.erase(streamId);
|
||||
streamInfos[idx++] = mStreamMap.at(streamId);
|
||||
mStreamMap.erase(streamId);
|
||||
}
|
||||
}
|
||||
|
||||
fillOfflineSessionInfo(offlineStreams, offlineReqs, circulatingBuffers, info);
|
||||
|
||||
// create the offline session object
|
||||
bool afTrigger;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mAfTriggerLock);
|
||||
afTrigger = mAfTrigger;
|
||||
}
|
||||
sp<ExternalCameraOfflineSession> sessionImpl = new ExternalCameraOfflineSession(
|
||||
mCroppingType, mCameraCharacteristics, mCameraId,
|
||||
mExifMake, mExifModel, mBlobBufferSize, afTrigger,
|
||||
streamInfos, offlineReqs, circulatingBuffers);
|
||||
|
||||
bool initFailed = sessionImpl->initialize();
|
||||
if (initFailed) {
|
||||
ALOGE("%s: offline session initialize failed!", __FUNCTION__);
|
||||
return Status::INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
// cleanup stream and buffer caches
|
||||
{
|
||||
Mutex::Autolock _l(mCbsLock);
|
||||
for(auto pair : mStreamMap) {
|
||||
cleanupBuffersLocked(/*Stream ID*/pair.first);
|
||||
}
|
||||
mCirculatingBuffers.clear();
|
||||
}
|
||||
mStreamMap.clear();
|
||||
|
||||
// update inflight records
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mInflightFramesLock);
|
||||
mInflightFrames.clear();
|
||||
}
|
||||
|
||||
// stop v4l2 streaming
|
||||
if (v4l2StreamOffLocked() !=0) {
|
||||
ALOGE("%s: stop V4L2 streaming failed!", __FUNCTION__);
|
||||
return Status::INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
*session = sessionImpl->getInterface();
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace V3_6
|
||||
} // namespace device
|
||||
|
||||
554
camera/device/3.6/default/ExternalCameraOfflineSession.cpp
Normal file
554
camera/device/3.6/default/ExternalCameraOfflineSession.cpp
Normal file
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "ExtCamOfflnSsn@3.6"
|
||||
#define ATRACE_TAG ATRACE_TAG_CAMERA
|
||||
#include <android/log.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <sync/sync.h>
|
||||
|
||||
#define HAVE_JPEG // required for libyuv.h to export MJPEG decode APIs
|
||||
#include <libyuv.h>
|
||||
|
||||
#include <utils/Trace.h>
|
||||
#include "ExternalCameraOfflineSession.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Size of request/result metadata fast message queue. Change to 0 to always use hwbinder buffer.
|
||||
static constexpr size_t kMetadataMsgQueueSize = 1 << 18 /* 256kB */;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
namespace camera {
|
||||
namespace device {
|
||||
namespace V3_6 {
|
||||
namespace implementation {
|
||||
|
||||
// static instance
|
||||
HandleImporter ExternalCameraOfflineSession::sHandleImporter;
|
||||
|
||||
using V3_5::implementation::ExternalCameraDeviceSession;
|
||||
|
||||
ExternalCameraOfflineSession::ExternalCameraOfflineSession(
|
||||
const CroppingType& croppingType,
|
||||
const common::V1_0::helper::CameraMetadata& chars,
|
||||
const std::string& cameraId,
|
||||
const std::string& exifMake,
|
||||
const std::string& exifModel,
|
||||
const uint32_t blobBufferSize,
|
||||
const bool afTrigger,
|
||||
const hidl_vec<Stream>& offlineStreams,
|
||||
std::deque<std::shared_ptr<HalRequest>>& offlineReqs,
|
||||
const std::map<int, CirculatingBuffers>& circulatingBuffers) :
|
||||
mCroppingType(croppingType), mChars(chars), mCameraId(cameraId),
|
||||
mExifMake(exifMake), mExifModel(exifModel), mBlobBufferSize(blobBufferSize),
|
||||
mAfTrigger(afTrigger), mOfflineStreams(offlineStreams), mOfflineReqs(offlineReqs),
|
||||
mCirculatingBuffers(circulatingBuffers) {}
|
||||
|
||||
ExternalCameraOfflineSession::~ExternalCameraOfflineSession() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool ExternalCameraOfflineSession::initialize() {
|
||||
mResultMetadataQueue = std::make_shared<ResultMetadataQueue>(
|
||||
kMetadataMsgQueueSize, false /* non blocking */);
|
||||
if (!mResultMetadataQueue->isValid()) {
|
||||
ALOGE("%s: invalid result fmq", __FUNCTION__);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExternalCameraOfflineSession::initOutputThread() {
|
||||
if (mOutputThread != nullptr) {
|
||||
ALOGE("%s: OutputThread already exist!", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
mBufferRequestThread = new ExternalCameraDeviceSession::BufferRequestThread(
|
||||
this, mCallback);
|
||||
mBufferRequestThread->run("ExtCamBufReq", PRIORITY_DISPLAY);
|
||||
|
||||
mOutputThread = new OutputThread(this, mCroppingType, mChars,
|
||||
mBufferRequestThread, mOfflineReqs);
|
||||
|
||||
mOutputThread->setExifMakeModel(mExifMake, mExifModel);
|
||||
|
||||
Size inputSize = { mOfflineReqs[0]->frameIn->mWidth, mOfflineReqs[0]->frameIn->mHeight};
|
||||
Size maxThumbSize = V3_4::implementation::getMaxThumbnailResolution(mChars);
|
||||
mOutputThread->allocateIntermediateBuffers(
|
||||
inputSize, maxThumbSize, mOfflineStreams, mBlobBufferSize);
|
||||
|
||||
mOutputThread->run("ExtCamOfflnOut", PRIORITY_DISPLAY);
|
||||
}
|
||||
|
||||
bool ExternalCameraOfflineSession::OutputThread::threadLoop() {
|
||||
auto parent = mParent.promote();
|
||||
if (parent == nullptr) {
|
||||
ALOGE("%s: session has been disconnected!", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mOfflineReqs.empty()) {
|
||||
ALOGI("%s: all offline requests are processed. Stopping.", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<HalRequest> req = mOfflineReqs.front();
|
||||
mOfflineReqs.pop_front();
|
||||
|
||||
auto onDeviceError = [&](auto... args) {
|
||||
ALOGE(args...);
|
||||
parent->notifyError(
|
||||
req->frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE);
|
||||
signalRequestDone();
|
||||
return false;
|
||||
};
|
||||
|
||||
if (req->frameIn->mFourcc != V4L2_PIX_FMT_MJPEG && req->frameIn->mFourcc != V4L2_PIX_FMT_Z16) {
|
||||
return onDeviceError("%s: do not support V4L2 format %c%c%c%c", __FUNCTION__,
|
||||
req->frameIn->mFourcc & 0xFF,
|
||||
(req->frameIn->mFourcc >> 8) & 0xFF,
|
||||
(req->frameIn->mFourcc >> 16) & 0xFF,
|
||||
(req->frameIn->mFourcc >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
int res = requestBufferStart(req->buffers);
|
||||
if (res != 0) {
|
||||
ALOGE("%s: send BufferRequest failed! res %d", __FUNCTION__, res);
|
||||
return onDeviceError("%s: failed to send buffer request!", __FUNCTION__);
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lk(mBufferLock);
|
||||
// Convert input V4L2 frame to YU12 of the same size
|
||||
// TODO: see if we can save some computation by converting to YV12 here
|
||||
uint8_t* inData;
|
||||
size_t inDataSize;
|
||||
if (req->frameIn->getData(&inData, &inDataSize) != 0) {
|
||||
lk.unlock();
|
||||
return onDeviceError("%s: V4L2 buffer map failed", __FUNCTION__);
|
||||
}
|
||||
|
||||
// TODO: in some special case maybe we can decode jpg directly to gralloc output?
|
||||
if (req->frameIn->mFourcc == V4L2_PIX_FMT_MJPEG) {
|
||||
ATRACE_BEGIN("MJPGtoI420");
|
||||
int res = libyuv::MJPGToI420(
|
||||
inData, inDataSize, static_cast<uint8_t*>(mYu12FrameLayout.y), mYu12FrameLayout.yStride,
|
||||
static_cast<uint8_t*>(mYu12FrameLayout.cb), mYu12FrameLayout.cStride,
|
||||
static_cast<uint8_t*>(mYu12FrameLayout.cr), mYu12FrameLayout.cStride,
|
||||
mYu12Frame->mWidth, mYu12Frame->mHeight, mYu12Frame->mWidth, mYu12Frame->mHeight);
|
||||
ATRACE_END();
|
||||
|
||||
if (res != 0) {
|
||||
// For some webcam, the first few V4L2 frames might be malformed...
|
||||
ALOGE("%s: Convert V4L2 frame to YU12 failed! res %d", __FUNCTION__, res);
|
||||
lk.unlock();
|
||||
Status st = parent->processCaptureRequestError(req);
|
||||
if (st != Status::OK) {
|
||||
return onDeviceError("%s: failed to process capture request error!", __FUNCTION__);
|
||||
}
|
||||
signalRequestDone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ATRACE_BEGIN("Wait for BufferRequest done");
|
||||
res = waitForBufferRequestDone(&req->buffers);
|
||||
ATRACE_END();
|
||||
|
||||
if (res != 0) {
|
||||
ALOGE("%s: wait for BufferRequest done failed! res %d", __FUNCTION__, res);
|
||||
lk.unlock();
|
||||
return onDeviceError("%s: failed to process buffer request error!", __FUNCTION__);
|
||||
}
|
||||
|
||||
ALOGV("%s processing new request", __FUNCTION__);
|
||||
const int kSyncWaitTimeoutMs = 500;
|
||||
for (auto& halBuf : req->buffers) {
|
||||
if (*(halBuf.bufPtr) == nullptr) {
|
||||
ALOGW("%s: buffer for stream %d missing", __FUNCTION__, halBuf.streamId);
|
||||
halBuf.fenceTimeout = true;
|
||||
} else if (halBuf.acquireFence >= 0) {
|
||||
int ret = sync_wait(halBuf.acquireFence, kSyncWaitTimeoutMs);
|
||||
if (ret) {
|
||||
halBuf.fenceTimeout = true;
|
||||
} else {
|
||||
::close(halBuf.acquireFence);
|
||||
halBuf.acquireFence = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (halBuf.fenceTimeout) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gralloc lockYCbCr the buffer
|
||||
switch (halBuf.format) {
|
||||
case PixelFormat::BLOB: {
|
||||
int ret = createJpegLocked(halBuf, req->setting);
|
||||
|
||||
if(ret != 0) {
|
||||
lk.unlock();
|
||||
return onDeviceError("%s: createJpegLocked failed with %d",
|
||||
__FUNCTION__, ret);
|
||||
}
|
||||
} break;
|
||||
case PixelFormat::Y16: {
|
||||
void* outLayout = sHandleImporter.lock(*(halBuf.bufPtr), halBuf.usage, inDataSize);
|
||||
|
||||
std::memcpy(outLayout, inData, inDataSize);
|
||||
|
||||
int relFence = sHandleImporter.unlock(*(halBuf.bufPtr));
|
||||
if (relFence >= 0) {
|
||||
halBuf.acquireFence = relFence;
|
||||
}
|
||||
} break;
|
||||
case PixelFormat::YCBCR_420_888:
|
||||
case PixelFormat::YV12: {
|
||||
IMapper::Rect outRect {0, 0,
|
||||
static_cast<int32_t>(halBuf.width),
|
||||
static_cast<int32_t>(halBuf.height)};
|
||||
YCbCrLayout outLayout = sHandleImporter.lockYCbCr(
|
||||
*(halBuf.bufPtr), halBuf.usage, outRect);
|
||||
ALOGV("%s: outLayout y %p cb %p cr %p y_str %d c_str %d c_step %d",
|
||||
__FUNCTION__, outLayout.y, outLayout.cb, outLayout.cr,
|
||||
outLayout.yStride, outLayout.cStride, outLayout.chromaStep);
|
||||
|
||||
// Convert to output buffer size/format
|
||||
uint32_t outputFourcc = V3_4::implementation::getFourCcFromLayout(outLayout);
|
||||
ALOGV("%s: converting to format %c%c%c%c", __FUNCTION__,
|
||||
outputFourcc & 0xFF,
|
||||
(outputFourcc >> 8) & 0xFF,
|
||||
(outputFourcc >> 16) & 0xFF,
|
||||
(outputFourcc >> 24) & 0xFF);
|
||||
|
||||
YCbCrLayout cropAndScaled;
|
||||
ATRACE_BEGIN("cropAndScaleLocked");
|
||||
int ret = cropAndScaleLocked(
|
||||
mYu12Frame,
|
||||
Size { halBuf.width, halBuf.height },
|
||||
&cropAndScaled);
|
||||
ATRACE_END();
|
||||
if (ret != 0) {
|
||||
lk.unlock();
|
||||
return onDeviceError("%s: crop and scale failed!", __FUNCTION__);
|
||||
}
|
||||
|
||||
Size sz {halBuf.width, halBuf.height};
|
||||
ATRACE_BEGIN("formatConvert");
|
||||
ret = V3_4::implementation::formatConvert(cropAndScaled, outLayout, sz, outputFourcc);
|
||||
ATRACE_END();
|
||||
if (ret != 0) {
|
||||
lk.unlock();
|
||||
return onDeviceError("%s: format coversion failed!", __FUNCTION__);
|
||||
}
|
||||
int relFence = sHandleImporter.unlock(*(halBuf.bufPtr));
|
||||
if (relFence >= 0) {
|
||||
halBuf.acquireFence = relFence;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
lk.unlock();
|
||||
return onDeviceError("%s: unknown output format %x", __FUNCTION__, halBuf.format);
|
||||
}
|
||||
} // for each buffer
|
||||
mScaledYu12Frames.clear();
|
||||
|
||||
// Don't hold the lock while calling back to parent
|
||||
lk.unlock();
|
||||
Status st = parent->processCaptureResult(req);
|
||||
if (st != Status::OK) {
|
||||
return onDeviceError("%s: failed to process capture result!", __FUNCTION__);
|
||||
}
|
||||
signalRequestDone();
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ExternalCameraOfflineSession::importBuffer(int32_t streamId,
|
||||
uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr,
|
||||
bool allowEmptyBuf) {
|
||||
Mutex::Autolock _l(mCbsLock);
|
||||
return V3_4::implementation::importBufferImpl(
|
||||
mCirculatingBuffers, sHandleImporter, streamId,
|
||||
bufId, buf, outBufPtr, allowEmptyBuf);
|
||||
return Status::OK;
|
||||
};
|
||||
|
||||
#define UPDATE(md, tag, data, size) \
|
||||
do { \
|
||||
if ((md).update((tag), (data), (size))) { \
|
||||
ALOGE("Update " #tag " failed!"); \
|
||||
return BAD_VALUE; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
status_t ExternalCameraOfflineSession::fillCaptureResult(
|
||||
common::V1_0::helper::CameraMetadata &md, nsecs_t timestamp) {
|
||||
bool afTrigger = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mAfTriggerLock);
|
||||
afTrigger = mAfTrigger;
|
||||
if (md.exists(ANDROID_CONTROL_AF_TRIGGER)) {
|
||||
camera_metadata_entry entry = md.find(ANDROID_CONTROL_AF_TRIGGER);
|
||||
if (entry.data.u8[0] == ANDROID_CONTROL_AF_TRIGGER_START) {
|
||||
mAfTrigger = afTrigger = true;
|
||||
} else if (entry.data.u8[0] == ANDROID_CONTROL_AF_TRIGGER_CANCEL) {
|
||||
mAfTrigger = afTrigger = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For USB camera, the USB camera handles everything and we don't have control
|
||||
// over AF. We only simply fake the AF metadata based on the request
|
||||
// received here.
|
||||
uint8_t afState;
|
||||
if (afTrigger) {
|
||||
afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
|
||||
} else {
|
||||
afState = ANDROID_CONTROL_AF_STATE_INACTIVE;
|
||||
}
|
||||
UPDATE(md, ANDROID_CONTROL_AF_STATE, &afState, 1);
|
||||
|
||||
camera_metadata_ro_entry activeArraySize =
|
||||
mChars.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
|
||||
|
||||
return V3_4::implementation::fillCaptureResultCommon(md, timestamp, activeArraySize);
|
||||
}
|
||||
|
||||
#undef UPDATE
|
||||
|
||||
Status ExternalCameraOfflineSession::processCaptureResult(std::shared_ptr<HalRequest>& req) {
|
||||
ATRACE_CALL();
|
||||
// Fill output buffers
|
||||
hidl_vec<CaptureResult> results;
|
||||
results.resize(1);
|
||||
CaptureResult& result = results[0];
|
||||
result.frameNumber = req->frameNumber;
|
||||
result.partialResult = 1;
|
||||
result.inputBuffer.streamId = -1;
|
||||
result.outputBuffers.resize(req->buffers.size());
|
||||
for (size_t i = 0; i < req->buffers.size(); i++) {
|
||||
result.outputBuffers[i].streamId = req->buffers[i].streamId;
|
||||
result.outputBuffers[i].bufferId = req->buffers[i].bufferId;
|
||||
if (req->buffers[i].fenceTimeout) {
|
||||
result.outputBuffers[i].status = BufferStatus::ERROR;
|
||||
if (req->buffers[i].acquireFence >= 0) {
|
||||
native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
|
||||
handle->data[0] = req->buffers[i].acquireFence;
|
||||
result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false);
|
||||
}
|
||||
notifyError(req->frameNumber, req->buffers[i].streamId, ErrorCode::ERROR_BUFFER);
|
||||
} else {
|
||||
result.outputBuffers[i].status = BufferStatus::OK;
|
||||
// TODO: refactor
|
||||
if (req->buffers[i].acquireFence >= 0) {
|
||||
native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
|
||||
handle->data[0] = req->buffers[i].acquireFence;
|
||||
result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill capture result metadata
|
||||
fillCaptureResult(req->setting, req->shutterTs);
|
||||
const camera_metadata_t *rawResult = req->setting.getAndLock();
|
||||
V3_2::implementation::convertToHidl(rawResult, &result.result);
|
||||
req->setting.unlock(rawResult);
|
||||
|
||||
// Callback into framework
|
||||
invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true);
|
||||
V3_4::implementation::freeReleaseFences(results);
|
||||
return Status::OK;
|
||||
};
|
||||
|
||||
void ExternalCameraOfflineSession::invokeProcessCaptureResultCallback(
|
||||
hidl_vec<CaptureResult> &results, bool tryWriteFmq) {
|
||||
if (mProcessCaptureResultLock.tryLock() != OK) {
|
||||
const nsecs_t NS_TO_SECOND = 1000000000;
|
||||
ALOGV("%s: previous call is not finished! waiting 1s...", __FUNCTION__);
|
||||
if (mProcessCaptureResultLock.timedLock(/* 1s */NS_TO_SECOND) != OK) {
|
||||
ALOGE("%s: cannot acquire lock in 1s, cannot proceed",
|
||||
__FUNCTION__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (tryWriteFmq && mResultMetadataQueue->availableToWrite() > 0) {
|
||||
for (CaptureResult &result : results) {
|
||||
if (result.result.size() > 0) {
|
||||
if (mResultMetadataQueue->write(result.result.data(), result.result.size())) {
|
||||
result.fmqResultSize = result.result.size();
|
||||
result.result.resize(0);
|
||||
} else {
|
||||
ALOGW("%s: couldn't utilize fmq, fall back to hwbinder", __FUNCTION__);
|
||||
result.fmqResultSize = 0;
|
||||
}
|
||||
} else {
|
||||
result.fmqResultSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto status = mCallback->processCaptureResult(results);
|
||||
if (!status.isOk()) {
|
||||
ALOGE("%s: processCaptureResult ERROR : %s", __FUNCTION__,
|
||||
status.description().c_str());
|
||||
}
|
||||
|
||||
mProcessCaptureResultLock.unlock();
|
||||
}
|
||||
|
||||
Status ExternalCameraOfflineSession::processCaptureRequestError(
|
||||
const std::shared_ptr<HalRequest>& req,
|
||||
/*out*/std::vector<NotifyMsg>* outMsgs,
|
||||
/*out*/std::vector<CaptureResult>* outResults) {
|
||||
ATRACE_CALL();
|
||||
|
||||
if (outMsgs == nullptr) {
|
||||
notifyError(/*frameNum*/req->frameNumber, /*stream*/-1, ErrorCode::ERROR_REQUEST);
|
||||
} else {
|
||||
NotifyMsg shutter;
|
||||
shutter.type = MsgType::SHUTTER;
|
||||
shutter.msg.shutter.frameNumber = req->frameNumber;
|
||||
shutter.msg.shutter.timestamp = req->shutterTs;
|
||||
|
||||
NotifyMsg error;
|
||||
error.type = MsgType::ERROR;
|
||||
error.msg.error.frameNumber = req->frameNumber;
|
||||
error.msg.error.errorStreamId = -1;
|
||||
error.msg.error.errorCode = ErrorCode::ERROR_REQUEST;
|
||||
outMsgs->push_back(shutter);
|
||||
outMsgs->push_back(error);
|
||||
}
|
||||
|
||||
// Fill output buffers
|
||||
hidl_vec<CaptureResult> results;
|
||||
results.resize(1);
|
||||
CaptureResult& result = results[0];
|
||||
result.frameNumber = req->frameNumber;
|
||||
result.partialResult = 1;
|
||||
result.inputBuffer.streamId = -1;
|
||||
result.outputBuffers.resize(req->buffers.size());
|
||||
for (size_t i = 0; i < req->buffers.size(); i++) {
|
||||
result.outputBuffers[i].streamId = req->buffers[i].streamId;
|
||||
result.outputBuffers[i].bufferId = req->buffers[i].bufferId;
|
||||
result.outputBuffers[i].status = BufferStatus::ERROR;
|
||||
if (req->buffers[i].acquireFence >= 0) {
|
||||
native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
|
||||
handle->data[0] = req->buffers[i].acquireFence;
|
||||
result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false);
|
||||
}
|
||||
}
|
||||
|
||||
if (outResults == nullptr) {
|
||||
// Callback into framework
|
||||
invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true);
|
||||
V3_4::implementation::freeReleaseFences(results);
|
||||
} else {
|
||||
outResults->push_back(result);
|
||||
}
|
||||
return Status::OK;
|
||||
};
|
||||
|
||||
ssize_t ExternalCameraOfflineSession::getJpegBufferSize(
|
||||
uint32_t /*width*/, uint32_t /*height*/) const {
|
||||
// Empty implementation here as the jpeg buffer size is passed in by ctor
|
||||
return 0;
|
||||
};
|
||||
|
||||
void ExternalCameraOfflineSession::notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) {
|
||||
NotifyMsg msg;
|
||||
msg.type = MsgType::ERROR;
|
||||
msg.msg.error.frameNumber = frameNumber;
|
||||
msg.msg.error.errorStreamId = streamId;
|
||||
msg.msg.error.errorCode = ec;
|
||||
mCallback->notify({msg});
|
||||
};
|
||||
|
||||
Return<void> ExternalCameraOfflineSession::setCallback(const sp<ICameraDeviceCallback>& cb) {
|
||||
Mutex::Autolock _il(mInterfaceLock);
|
||||
if (mCallback != nullptr && cb != nullptr) {
|
||||
ALOGE("%s: callback must not be set twice!", __FUNCTION__);
|
||||
return Void();
|
||||
}
|
||||
mCallback = cb;
|
||||
|
||||
initOutputThread();
|
||||
|
||||
if (mOutputThread == nullptr) {
|
||||
ALOGE("%s: init OutputThread failed!", __FUNCTION__);
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
||||
Return<void> ExternalCameraOfflineSession::getCaptureResultMetadataQueue(
|
||||
V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) {
|
||||
Mutex::Autolock _il(mInterfaceLock);
|
||||
_hidl_cb(*mResultMetadataQueue->getDesc());
|
||||
return Void();
|
||||
}
|
||||
|
||||
void ExternalCameraOfflineSession::cleanupBuffersLocked(int id) {
|
||||
for (auto& pair : mCirculatingBuffers.at(id)) {
|
||||
sHandleImporter.freeBuffer(pair.second);
|
||||
}
|
||||
mCirculatingBuffers[id].clear();
|
||||
mCirculatingBuffers.erase(id);
|
||||
}
|
||||
|
||||
Return<void> ExternalCameraOfflineSession::close() {
|
||||
Mutex::Autolock _il(mInterfaceLock);
|
||||
{
|
||||
Mutex::Autolock _l(mLock);
|
||||
if (mClosed) {
|
||||
ALOGW("%s: offline session already closed!", __FUNCTION__);
|
||||
return Void();
|
||||
}
|
||||
}
|
||||
if (mBufferRequestThread) {
|
||||
mBufferRequestThread->requestExit();
|
||||
mBufferRequestThread->join();
|
||||
mBufferRequestThread.clear();
|
||||
}
|
||||
if (mOutputThread) {
|
||||
mOutputThread->flush();
|
||||
mOutputThread->requestExit();
|
||||
mOutputThread->join();
|
||||
mOutputThread.clear();
|
||||
}
|
||||
|
||||
Mutex::Autolock _l(mLock);
|
||||
// free all buffers
|
||||
{
|
||||
Mutex::Autolock _cbl(mCbsLock);
|
||||
for(auto stream : mOfflineStreams) {
|
||||
cleanupBuffersLocked(stream.id);
|
||||
}
|
||||
}
|
||||
mCallback.clear();
|
||||
mClosed = true;
|
||||
return Void();
|
||||
}
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace V3_6
|
||||
} // namespace device
|
||||
} // namespace camera
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
@@ -14,12 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICE3SESSION_H
|
||||
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICE3SESSION_H
|
||||
#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICESESSION_H
|
||||
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICESESSION_H
|
||||
|
||||
#include <android/hardware/camera/device/3.5/ICameraDeviceCallback.h>
|
||||
#include <android/hardware/camera/device/3.6/ICameraDeviceSession.h>
|
||||
#include <../../3.5/default/include/ext_device_v3_5_impl/ExternalCameraDeviceSession.h>
|
||||
#include "ExternalCameraOfflineSession.h"
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -37,6 +38,7 @@ using ::android::hardware::camera::device::V3_2::RequestTemplate;
|
||||
using ::android::hardware::camera::device::V3_2::Stream;
|
||||
using ::android::hardware::camera::device::V3_5::StreamConfiguration;
|
||||
using ::android::hardware::camera::device::V3_6::ICameraDeviceSession;
|
||||
using ::android::hardware::camera::device::V3_6::ICameraOfflineSession;
|
||||
using ::android::hardware::camera::common::V1_0::Status;
|
||||
using ::android::hardware::camera::external::common::ExternalCameraConfig;
|
||||
using ::android::hardware::graphics::common::V1_0::PixelFormat;
|
||||
@@ -69,13 +71,6 @@ struct ExternalCameraDeviceSession : public V3_5::implementation::ExternalCamera
|
||||
return new TrampolineSessionInterface_3_6(this);
|
||||
}
|
||||
|
||||
static Status isStreamCombinationSupported(const V3_2::StreamConfiguration& config,
|
||||
const std::vector<SupportedV4L2Format>& supportedFormats,
|
||||
const ExternalCameraConfig& devCfg) {
|
||||
return V3_4::implementation::ExternalCameraDeviceSession::isStreamCombinationSupported(
|
||||
config, supportedFormats, devCfg);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Methods from v3.5 and earlier will trampoline to inherited implementation
|
||||
Return<void> configureStreams_3_6(
|
||||
@@ -86,6 +81,28 @@ protected:
|
||||
const hidl_vec<int32_t>& streamsToKeep,
|
||||
ICameraDeviceSession::switchToOffline_cb _hidl_cb);
|
||||
|
||||
void fillOutputStream3_6(const V3_3::HalStreamConfiguration& outStreams_v33,
|
||||
/*out*/V3_6::HalStreamConfiguration* outStreams_v36);
|
||||
bool supportOfflineLocked(int32_t streamId);
|
||||
|
||||
// Main body of switchToOffline. This method does not invoke any callbacks
|
||||
// but instead returns the necessary callbacks in output arguments so callers
|
||||
// can callback later without holding any locks
|
||||
Status switchToOffline(const hidl_vec<int32_t>& offlineStreams,
|
||||
/*out*/std::vector<NotifyMsg>* msgs,
|
||||
/*out*/std::vector<CaptureResult>* results,
|
||||
/*out*/CameraOfflineSessionInfo* info,
|
||||
/*out*/sp<ICameraOfflineSession>* session);
|
||||
|
||||
// Whether a request can be completely dropped when switching to offline
|
||||
bool canDropRequest(const hidl_vec<int32_t>& offlineStreams,
|
||||
std::shared_ptr<V3_4::implementation::HalRequest> halReq);
|
||||
|
||||
void fillOfflineSessionInfo(const hidl_vec<int32_t>& offlineStreams,
|
||||
std::deque<std::shared_ptr<HalRequest>>& offlineReqs,
|
||||
const std::map<int, CirculatingBuffers>& circulatingBuffers,
|
||||
/*out*/CameraOfflineSessionInfo* info);
|
||||
|
||||
private:
|
||||
|
||||
struct TrampolineSessionInterface_3_6 : public ICameraDeviceSession {
|
||||
@@ -188,4 +205,4 @@ private:
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICE3SESSION_H
|
||||
#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICESESSION_H
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERAOFFLINESESSION_H
|
||||
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERAOFFLINESESSION_H
|
||||
|
||||
#include <android/hardware/camera/device/3.5/ICameraDeviceCallback.h>
|
||||
#include <android/hardware/camera/device/3.6/ICameraDeviceSession.h>
|
||||
#include <android/hardware/camera/device/3.6/ICameraOfflineSession.h>
|
||||
#include <android/hardware/camera/common/1.0/types.h>
|
||||
#include <fmq/MessageQueue.h>
|
||||
#include <hidl/MQDescriptor.h>
|
||||
#include <deque>
|
||||
#include <../../3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h>
|
||||
#include <../../3.5/default/include/ext_device_v3_5_impl/ExternalCameraDeviceSession.h>
|
||||
#include <HandleImporter.h>
|
||||
#include <Exif.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
namespace camera {
|
||||
namespace device {
|
||||
namespace V3_6 {
|
||||
namespace implementation {
|
||||
|
||||
using ::android::hardware::camera::device::V3_2::BufferCache;
|
||||
using ::android::hardware::camera::device::V3_5::BufferRequest;
|
||||
using ::android::hardware::camera::device::V3_5::BufferRequestStatus;
|
||||
using ::android::hardware::camera::device::V3_2::BufferStatus;
|
||||
using ::android::hardware::camera::device::V3_2::CameraMetadata;
|
||||
using ::android::hardware::camera::device::V3_2::CaptureRequest;
|
||||
using ::android::hardware::camera::device::V3_2::CaptureResult;
|
||||
using ::android::hardware::camera::device::V3_2::ErrorCode;
|
||||
using ::android::hardware::camera::device::V3_5::ICameraDeviceCallback;
|
||||
using ::android::hardware::camera::device::V3_2::MsgType;
|
||||
using ::android::hardware::camera::device::V3_2::NotifyMsg;
|
||||
using ::android::hardware::camera::device::V3_2::RequestTemplate;
|
||||
using ::android::hardware::camera::device::V3_2::Stream;
|
||||
using ::android::hardware::camera::device::V3_5::StreamConfiguration;
|
||||
using ::android::hardware::camera::device::V3_2::StreamConfigurationMode;
|
||||
using ::android::hardware::camera::device::V3_2::StreamRotation;
|
||||
using ::android::hardware::camera::device::V3_2::StreamType;
|
||||
using ::android::hardware::camera::device::V3_2::DataspaceFlags;
|
||||
using ::android::hardware::camera::device::V3_2::CameraBlob;
|
||||
using ::android::hardware::camera::device::V3_2::CameraBlobId;
|
||||
using ::android::hardware::camera::device::V3_4::HalStreamConfiguration;
|
||||
using ::android::hardware::camera::device::V3_6::ICameraOfflineSession;
|
||||
using ::android::hardware::camera::common::V1_0::Status;
|
||||
using ::android::hardware::camera::common::V1_0::helper::HandleImporter;
|
||||
using ::android::hardware::camera::common::V1_0::helper::ExifUtils;
|
||||
using ::android::hardware::camera::external::common::ExternalCameraConfig;
|
||||
using ::android::hardware::camera::external::common::Size;
|
||||
using ::android::hardware::camera::external::common::SizeHasher;
|
||||
using ::android::hardware::graphics::common::V1_0::BufferUsage;
|
||||
using ::android::hardware::graphics::common::V1_0::Dataspace;
|
||||
using ::android::hardware::graphics::common::V1_0::PixelFormat;
|
||||
using ::android::hardware::kSynchronizedReadWrite;
|
||||
using ::android::hardware::MessageQueue;
|
||||
using ::android::hardware::MQDescriptorSync;
|
||||
using ::android::hardware::Return;
|
||||
using ::android::hardware::Void;
|
||||
using ::android::hardware::hidl_vec;
|
||||
using ::android::hardware::hidl_string;
|
||||
using ::android::sp;
|
||||
using ::android::Mutex;
|
||||
using ::android::base::unique_fd;
|
||||
|
||||
using ::android::hardware::camera::device::V3_4::implementation::SupportedV4L2Format;
|
||||
using ::android::hardware::camera::device::V3_4::implementation::CroppingType;
|
||||
using ::android::hardware::camera::device::V3_4::implementation::CirculatingBuffers;
|
||||
using ::android::hardware::camera::device::V3_4::implementation::HalRequest;
|
||||
using ::android::hardware::camera::device::V3_4::implementation::OutputThreadInterface;
|
||||
|
||||
struct ExternalCameraOfflineSession : public virtual RefBase,
|
||||
public virtual OutputThreadInterface {
|
||||
|
||||
ExternalCameraOfflineSession(
|
||||
const CroppingType& croppingType,
|
||||
const common::V1_0::helper::CameraMetadata& chars,
|
||||
const std::string& cameraId,
|
||||
const std::string& exifMake,
|
||||
const std::string& exifModel,
|
||||
uint32_t blobBufferSize,
|
||||
bool afTrigger,
|
||||
const hidl_vec<Stream>& offlineStreams,
|
||||
std::deque<std::shared_ptr<HalRequest>>& offlineReqs,
|
||||
const std::map<int, CirculatingBuffers>& circulatingBuffers);
|
||||
|
||||
bool initialize();
|
||||
|
||||
virtual ~ExternalCameraOfflineSession();
|
||||
|
||||
// Retrieve the HIDL interface, split into its own class to avoid inheritance issues when
|
||||
// dealing with minor version revs and simultaneous implementation and interface inheritance
|
||||
virtual sp<V3_6::ICameraOfflineSession> getInterface() {
|
||||
return new TrampolineSessionInterface_3_6(this);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// Methods from OutputThreadInterface
|
||||
virtual Status importBuffer(int32_t streamId,
|
||||
uint64_t bufId, buffer_handle_t buf,
|
||||
/*out*/buffer_handle_t** outBufPtr,
|
||||
bool allowEmptyBuf) override;
|
||||
|
||||
virtual Status processCaptureResult(std::shared_ptr<HalRequest>&) override;
|
||||
|
||||
virtual Status processCaptureRequestError(const std::shared_ptr<HalRequest>&,
|
||||
/*out*/std::vector<NotifyMsg>* msgs = nullptr,
|
||||
/*out*/std::vector<CaptureResult>* results = nullptr) override;
|
||||
|
||||
virtual ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const override;
|
||||
|
||||
virtual void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) override;
|
||||
// End of OutputThreadInterface methods
|
||||
|
||||
class OutputThread : public V3_5::implementation::ExternalCameraDeviceSession::OutputThread {
|
||||
public:
|
||||
OutputThread(
|
||||
wp<OutputThreadInterface> parent, CroppingType ct,
|
||||
const common::V1_0::helper::CameraMetadata& chars,
|
||||
sp<V3_5::implementation::ExternalCameraDeviceSession::BufferRequestThread> bufReqThread,
|
||||
std::deque<std::shared_ptr<HalRequest>>& offlineReqs) :
|
||||
V3_5::implementation::ExternalCameraDeviceSession::OutputThread(
|
||||
parent, ct, chars, bufReqThread),
|
||||
mOfflineReqs(offlineReqs) {}
|
||||
|
||||
virtual bool threadLoop() override;
|
||||
|
||||
protected:
|
||||
std::deque<std::shared_ptr<HalRequest>> mOfflineReqs;
|
||||
}; // OutputThread
|
||||
|
||||
|
||||
Return<void> setCallback(const sp<ICameraDeviceCallback>& cb);
|
||||
|
||||
Return<void> getCaptureResultMetadataQueue(
|
||||
V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb);
|
||||
|
||||
Return<void> close();
|
||||
|
||||
void initOutputThread();
|
||||
|
||||
void invokeProcessCaptureResultCallback(
|
||||
hidl_vec<CaptureResult> &results, bool tryWriteFmq);
|
||||
|
||||
status_t fillCaptureResult(common::V1_0::helper::CameraMetadata& md, nsecs_t timestamp);
|
||||
|
||||
void cleanupBuffersLocked(int id);
|
||||
|
||||
// Protect (most of) HIDL interface methods from synchronized-entering
|
||||
mutable Mutex mInterfaceLock;
|
||||
|
||||
mutable Mutex mLock; // Protect all data members except otherwise noted
|
||||
|
||||
bool mClosed = false;
|
||||
const CroppingType mCroppingType;
|
||||
const common::V1_0::helper::CameraMetadata mChars;
|
||||
const std::string mCameraId;
|
||||
const std::string mExifMake;
|
||||
const std::string mExifModel;
|
||||
const uint32_t mBlobBufferSize;
|
||||
|
||||
std::mutex mAfTriggerLock; // protect mAfTrigger
|
||||
bool mAfTrigger;
|
||||
|
||||
const hidl_vec<Stream> mOfflineStreams;
|
||||
std::deque<std::shared_ptr<HalRequest>> mOfflineReqs;
|
||||
|
||||
// Protect mCirculatingBuffers, must not lock mLock after acquiring this lock
|
||||
mutable Mutex mCbsLock;
|
||||
std::map<int, CirculatingBuffers> mCirculatingBuffers;
|
||||
|
||||
static HandleImporter sHandleImporter;
|
||||
|
||||
using ResultMetadataQueue = MessageQueue<uint8_t, kSynchronizedReadWrite>;
|
||||
std::shared_ptr<ResultMetadataQueue> mResultMetadataQueue;
|
||||
|
||||
// Protect against invokeProcessCaptureResultCallback()
|
||||
Mutex mProcessCaptureResultLock;
|
||||
|
||||
sp<ICameraDeviceCallback> mCallback;
|
||||
|
||||
sp<V3_5::implementation::ExternalCameraDeviceSession::BufferRequestThread> mBufferRequestThread;
|
||||
sp<OutputThread> mOutputThread;
|
||||
private:
|
||||
|
||||
struct TrampolineSessionInterface_3_6 : public ICameraOfflineSession {
|
||||
TrampolineSessionInterface_3_6(sp<ExternalCameraOfflineSession> parent) :
|
||||
mParent(parent) {}
|
||||
|
||||
virtual Return<void> setCallback(const sp<ICameraDeviceCallback>& cb) override {
|
||||
return mParent->setCallback(cb);
|
||||
}
|
||||
|
||||
virtual Return<void> getCaptureResultMetadataQueue(
|
||||
V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) override {
|
||||
return mParent->getCaptureResultMetadataQueue(_hidl_cb);
|
||||
}
|
||||
|
||||
virtual Return<void> close() override {
|
||||
return mParent->close();
|
||||
}
|
||||
|
||||
private:
|
||||
sp<ExternalCameraOfflineSession> mParent;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace V3_6
|
||||
} // namespace device
|
||||
} // namespace camera
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERAOFFLINESESSION_H
|
||||
@@ -52,6 +52,8 @@ cc_library_shared {
|
||||
"android.hardware.camera.provider@2.4-external",
|
||||
"android.hardware.camera.provider@2.5",
|
||||
"android.hardware.graphics.mapper@2.0",
|
||||
"android.hardware.graphics.mapper@3.0",
|
||||
"android.hardware.graphics.mapper@4.0",
|
||||
"android.hidl.allocator@1.0",
|
||||
"android.hidl.memory@1.0",
|
||||
"camera.device@3.3-impl",
|
||||
@@ -72,7 +74,8 @@ cc_library_shared {
|
||||
],
|
||||
header_libs: [
|
||||
"camera.device@3.4-external-impl_headers",
|
||||
"camera.device@3.5-external-impl_headers"
|
||||
"camera.device@3.5-external-impl_headers",
|
||||
"camera.device@3.6-external-impl_headers"
|
||||
],
|
||||
export_include_dirs: ["."],
|
||||
}
|
||||
@@ -165,7 +168,10 @@ cc_binary {
|
||||
"android.hardware.camera.provider@2.5",
|
||||
"android.hardware.camera.provider@2.5-external",
|
||||
"android.hardware.graphics.mapper@2.0",
|
||||
"android.hardware.graphics.mapper@3.0",
|
||||
"android.hardware.graphics.mapper@4.0",
|
||||
"libbinder",
|
||||
"libcamera_metadata",
|
||||
"libhidlbase",
|
||||
"liblog",
|
||||
"libtinyxml2",
|
||||
@@ -179,5 +185,6 @@ cc_binary {
|
||||
"camera.device@3.4-impl_headers",
|
||||
"camera.device@3.5-external-impl_headers",
|
||||
"camera.device@3.5-impl_headers",
|
||||
"camera.device@3.6-external-impl_headers",
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user