/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Uncomment LOG_NDEBUG to enable verbose logging, and uncomment both LOG_NDEBUG
// *and* LOG_NNDEBUG to enable very verbose logging.
//#define LOG_NDEBUG 0
//#define LOG_NNDEBUG 0
#define LOG_TAG "EmulatedCamera3_QemuSensor"
#ifdef LOG_NNDEBUG
#define ALOGVV(...) ALOGV(__VA_ARGS__)
#else
#define ALOGVV(...) ((void)0)
#endif
#include "qemu-pipeline3/QemuSensor.h"
#include "system/camera_metadata.h"
#include <cmath>
#include <cstdlib>
#include <linux/videodev2.h>
#include <utils/Log.h>
namespace android {
const nsecs_t QemuSensor::kExposureTimeRange[2] =
{1000L, 300000000L}; // 1 us - 0.3 sec
const nsecs_t QemuSensor::kFrameDurationRange[2] =
{33331760L, 300000000L}; // ~1/30 s - 0.3 sec
const nsecs_t QemuSensor::kMinVerticalBlank = 10000L;
const int32_t QemuSensor::kSensitivityRange[2] = {100, 1600};
const uint32_t QemuSensor::kDefaultSensitivity = 100;
QemuSensor::QemuSensor(const char *deviceName, uint32_t width, uint32_t height):
Thread(false),
mWidth(width),
mHeight(height),
mActiveArray{0, 0, width, height},
mLastRequestWidth(-1),
mLastRequestHeight(-1),
mCameraQemuClient(),
mGotVSync(false),
mDeviceName(deviceName),
mFrameDuration(kFrameDurationRange[0]),
mNextBuffers(nullptr),
mFrameNumber(0),
mCapturedBuffers(nullptr),
mListener(nullptr) {
ALOGV("QemuSensor created with pixel array %d x %d", width, height);
}
QemuSensor::~QemuSensor() {
shutDown();
}
status_t QemuSensor::startUp() {
ALOGV("%s: Entered", __FUNCTION__);
mCapturedBuffers = nullptr;
status_t res = run("EmulatedQemuCamera3::QemuSensor",
ANDROID_PRIORITY_URGENT_DISPLAY);
if (res != OK) {
ALOGE("Unable to start up sensor capture thread: %d", res);
}
char connect_str[256];
snprintf(connect_str, sizeof(connect_str), "name=%s", mDeviceName);
res = mCameraQemuClient.connectClient(connect_str);
if (res != NO_ERROR) {
return res;
}
res = mCameraQemuClient.queryConnect();
if (res == NO_ERROR) {
ALOGV("%s: Connected to device '%s'",
__FUNCTION__, (const char*) mDeviceName);
mState = ECDS_CONNECTED;
} else {
ALOGE("%s: Connection to device '%s' failed",
__FUNCTION__, (const char*) mDeviceName);
}
return res;
}
status_t QemuSensor::shutDown() {
ALOGV("%s: Entered", __FUNCTION__);
status_t res = requestExitAndWait();
if (res != OK) {
ALOGE("Unable to shut down sensor capture thread: %d", res);
}
/* Stop the actual camera device. */
res = mCameraQemuClient.queryStop();
if (res == NO_ERROR) {
mState = ECDS_CONNECTED;
ALOGV("%s: Qemu camera device '%s' is stopped",
__FUNCTION__, (const char*) mDeviceName);
} else {
ALOGE("%s: Unable to stop device '%s'",
__FUNCTION__, (const char*) mDeviceName);
}
return res;
}
void QemuSensor::setFrameDuration(uint64_t ns) {
Mutex::Autolock lock(mControlMutex);
ALOGVV("Frame duration set to %f", ns/1000000.f);
mFrameDuration = ns;
}
void QemuSensor::setDestinationBuffers(Buffers *buffers) {
Mutex::Autolock lock(mControlMutex);
mNextBuffers = buffers;
}
void QemuSensor::setFrameNumber(uint32_t frameNumber) {
Mutex::Autolock lock(mControlMutex);
mFrameNumber = frameNumber;
}
bool QemuSensor::waitForVSync(nsecs_t reltime) {
int res;
Mutex::Autolock lock(mControlMutex);
mGotVSync = false;
res = mVSync.waitRelative(mControlMutex, reltime);
if (res != OK && res != TIMED_OUT) {
ALOGE("%s: Error waiting for VSync signal: %d", __FUNCTION__, res);
return false;
}
return mGotVSync;
}
bool QemuSensor::waitForNewFrame(nsecs_t reltime, nsecs_t *captureTime) {
Mutex::Autolock lock(mReadoutMutex);
uint8_t *ret;
if (mCapturedBuffers == nullptr) {
int res;
res = mReadoutAvailable.waitRelative(mReadoutMutex, reltime);
if (res == TIMED_OUT) {
return false;
} else if (res != OK || mCapturedBuffers == nullptr) {
ALOGE("Error waiting for sensor readout signal: %d", res);
return false;
}
}
mReadoutComplete.signal();
*captureTime = mCaptureTime;
mCapturedBuffers = nullptr;
return true;
}
QemuSensor::QemuSensorListener::~QemuSensorListener() {
}
void QemuSensor::setQemuSensorListener(QemuSensorListener *listener) {
Mutex::Autolock lock(mControlMutex);
mListener = listener;
}
status_t QemuSensor::readyToRun() {
ALOGV("Starting up sensor thread");
mStartupTime = systemTime();
mNextCaptureTime = 0;
mNextCapturedBuffers = nullptr;
return OK;
}
bool QemuSensor::threadLoop() {
/*
* Stages are out-of-order relative to a single frame's processing, but
* in-order in time.
*/
/*
* Stage 1: Read in latest control parameters.
*/
uint64_t frameDuration;
Buffers *nextBuffers;
uint32_t frameNumber;
QemuSensorListener *listener = nullptr;
{
// Lock while we're grabbing readout variables.
Mutex::Autolock lock(mControlMutex);
frameDuration = mFrameDuration;
nextBuffers = mNextBuffers;
frameNumber = mFrameNumber;
listener = mListener;
// Don't reuse a buffer set.
mNextBuffers = nullptr;
// Signal VSync for start of readout.
ALOGVV("QemuSensor VSync");
mGotVSync = true;
mVSync.signal();
}
/*
* Stage 3: Read out latest captured image.
*/
Buffers *capturedBuffers = nullptr;
nsecs_t captureTime = 0;
nsecs_t startRealTime = systemTime();
/*
* Stagefright cares about system time for timestamps, so base simulated
* time on that.
*/
nsecs_t simulatedTime = startRealTime;
nsecs_t frameEndRealTime = startRealTime + frameDuration;
if (mNextCapturedBuffers != nullptr) {
ALOGVV("QemuSensor starting readout");
/*
* Pretend we're doing readout now; will signal once enough time has
* elapsed.
*/
capturedBuffers = mNextCapturedBuffers;
captureTime = mNextCaptureTime;
}
/*
* TODO: Move this signal to another thread to simulate readout time
* properly.
*/
if (capturedBuffers != nullptr) {
ALOGVV("QemuSensor readout complete");
Mutex::Autolock lock(mReadoutMutex);
if (mCapturedBuffers != nullptr) {
ALOGV("Waiting for readout thread to catch up!");
mReadoutComplete.wait(mReadoutMutex);
}
mCapturedBuffers = capturedBuffers;
mCaptureTime = captureTime;
mReadoutAvailable.signal();
capturedBuffers = nullptr;
}
/*
* Stage 2: Capture new image.
*/
mNextCaptureTime = simulatedTime;
mNextCapturedBuffers = nextBuffers;
if (mNextCapturedBuffers != nullptr) {
int64_t timestamp = 0L;
// Might be adding more buffers, so size isn't constant.
for (size_t i = 0; i < mNextCapturedBuffers->size(); ++i) {
const StreamBuffer &b = (*mNextCapturedBuffers)[i];
ALOGVV("QemuSensor capturing buffer %d: stream %d,"
" %d x %d, format %x, stride %d, buf %p, img %p",
i, b.streamId, b.width, b.height, b.format, b.stride,
b.buffer, b.img);
switch (b.format) {
case HAL_PIXEL_FORMAT_RGB_888:
captureRGB(b.img, b.width, b.height, b.stride, ×tamp);
break;
case HAL_PIXEL_FORMAT_RGBA_8888:
captureRGBA(b.img, b.width, b.height, b.stride, ×tamp);
break;
case HAL_PIXEL_FORMAT_BLOB:
if (b.dataSpace == HAL_DATASPACE_DEPTH) {
ALOGE("%s: Depth clouds unsupported", __FUNCTION__);
} else {
/*
* Add auxillary buffer of the right size. Assumes only
* one BLOB (JPEG) buffer is in mNextCapturedBuffers.
*/
StreamBuffer bAux;
bAux.streamId = 0;
bAux.width = b.width;
bAux.height = b.height;
bAux.format = HAL_PIXEL_FORMAT_YCbCr_420_888;
bAux.stride = b.width;
bAux.buffer = nullptr;
// TODO: Reuse these.
bAux.img = new uint8_t[b.width * b.height * 3];
mNextCapturedBuffers->push_back(bAux);
}
break;
case HAL_PIXEL_FORMAT_YCbCr_420_888:
captureNV21(b.img, b.width, b.height, b.stride, ×tamp);
break;
default:
ALOGE("%s: Unknown/unsupported format %x, no output",
__FUNCTION__, b.format);
break;
}
}
if (timestamp != 0UL) {
mNextCaptureTime = timestamp;
}
// Note: we have to do this after the actual capture so that the
// capture time is accurate as reported from QEMU.
if (listener != nullptr) {
listener->onQemuSensorEvent(frameNumber, QemuSensorListener::EXPOSURE_START,
mNextCaptureTime);
}
}
ALOGVV("QemuSensor vertical blanking interval");
nsecs_t workDoneRealTime = systemTime();
const nsecs_t timeAccuracy = 2e6; // 2 ms of imprecision is ok.
if (workDoneRealTime < frameEndRealTime - timeAccuracy) {
timespec t;
t.tv_sec = (frameEndRealTime - workDoneRealTime) / 1000000000L;
t.tv_nsec = (frameEndRealTime - workDoneRealTime) % 1000000000L;
int ret;
do {
ret = nanosleep(&t, &t);
} while (ret != 0);
}
nsecs_t endRealTime = systemTime();
ALOGVV("Frame cycle took %d ms, target %d ms",
(int) ((endRealTime - startRealTime) / 1000000),
(int) (frameDuration / 1000000));
return true;
};
void QemuSensor::captureRGBA(uint8_t *img, uint32_t width, uint32_t height,
uint32_t stride, int64_t *timestamp) {
status_t res;
if (width != mLastRequestWidth || height != mLastRequestHeight) {
ALOGI("%s: Dimensions for the current request (%dx%d) differ "
"from the previous request (%dx%d). Restarting camera",
__FUNCTION__, width, height, mLastRequestWidth,
mLastRequestHeight);
if (mLastRequestWidth != -1 || mLastRequestHeight != -1) {
// We only need to stop the camera if this isn't the first request.
// Stop the camera device.
res = mCameraQemuClient.queryStop();
if (res == NO_ERROR) {
mState = ECDS_CONNECTED;
ALOGV("%s: Qemu camera device '%s' is stopped",
__FUNCTION__, (const char*) mDeviceName);
} else {
ALOGE("%s: Unable to stop device '%s'",
__FUNCTION__, (const char*) mDeviceName);
}
}
/*
* Host Camera always assumes V4L2_PIX_FMT_RGB32 as the preview format,
* and asks for the video format from the pixFmt parameter, which is
* V4L2_PIX_FMT_NV21 in our implementation.
*/
uint32_t pixFmt = V4L2_PIX_FMT_NV21;
res = mCameraQemuClient.queryStart(pixFmt, width, height);
if (res == NO_ERROR) {
mLastRequestWidth = width;
mLastRequestHeight = height;
ALOGV("%s: Qemu camera device '%s' is started for %.4s[%dx%d] frames",
__FUNCTION__, (const char*) mDeviceName,
reinterpret_cast<const char*>(&pixFmt),
mWidth, mHeight);
mState = ECDS_STARTED;
} else {
ALOGE("%s: Unable to start device '%s' for %.4s[%dx%d] frames",
__FUNCTION__, (const char*) mDeviceName,
reinterpret_cast<const char*>(&pixFmt),
mWidth, mHeight);
return;
}
}
if (width != stride) {
ALOGW("%s: expect stride (%d), actual stride (%d)", __FUNCTION__,
width, stride);
}
// Since the format is V4L2_PIX_FMT_RGB32, we need 4 bytes per pixel.
size_t bufferSize = width * height * 4;
// Apply no white balance or exposure compensation.
float whiteBalance[] = {1.0f, 1.0f, 1.0f};
float exposureCompensation = 1.0f;
// Read from webcam.
mCameraQemuClient.queryFrame(nullptr, img, 0, bufferSize, whiteBalance[0],
whiteBalance[1], whiteBalance[2],
exposureCompensation, timestamp);
ALOGVV("RGBA sensor image captured");
}
void QemuSensor::captureRGB(uint8_t *img, uint32_t width, uint32_t height, uint32_t stride, int64_t *timestamp) {
ALOGE("%s: Not implemented", __FUNCTION__);
}
void QemuSensor::captureNV21(uint8_t *img, uint32_t width, uint32_t height, uint32_t stride, int64_t *timestamp) {
status_t res;
if (width != mLastRequestWidth || height != mLastRequestHeight) {
ALOGI("%s: Dimensions for the current request (%dx%d) differ "
"from the previous request (%dx%d). Restarting camera",
__FUNCTION__, width, height, mLastRequestWidth,
mLastRequestHeight);
if (mLastRequestWidth != -1 || mLastRequestHeight != -1) {
// We only need to stop the camera if this isn't the first request.
// Stop the camera device.
res = mCameraQemuClient.queryStop();
if (res == NO_ERROR) {
mState = ECDS_CONNECTED;
ALOGV("%s: Qemu camera device '%s' is stopped",
__FUNCTION__, (const char*) mDeviceName);
} else {
ALOGE("%s: Unable to stop device '%s'",
__FUNCTION__, (const char*) mDeviceName);
}
}
/*
* Host Camera always assumes V4L2_PIX_FMT_RGB32 as the preview format,
* and asks for the video format from the pixFmt parameter, which is
* V4L2_PIX_FMT_NV21 in our implementation.
*/
uint32_t pixFmt = V4L2_PIX_FMT_NV21;
res = mCameraQemuClient.queryStart(pixFmt, width, height);
if (res == NO_ERROR) {
mLastRequestWidth = width;
mLastRequestHeight = height;
ALOGV("%s: Qemu camera device '%s' is started for %.4s[%dx%d] frames",
__FUNCTION__, (const char*) mDeviceName,
reinterpret_cast<const char*>(&pixFmt),
mWidth, mHeight);
mState = ECDS_STARTED;
} else {
ALOGE("%s: Unable to start device '%s' for %.4s[%dx%d] frames",
__FUNCTION__, (const char*) mDeviceName,
reinterpret_cast<const char*>(&pixFmt),
mWidth, mHeight);
return;
}
}
if (width != stride) {
ALOGW("%s: expect stride (%d), actual stride (%d)", __FUNCTION__,
width, stride);
}
// Calculate the buffer size for NV21.
size_t bufferSize = (width * height * 12) / 8;
// Apply no white balance or exposure compensation.
float whiteBalance[] = {1.0f, 1.0f, 1.0f};
float exposureCompensation = 1.0f;
// Read video frame from webcam.
mCameraQemuClient.queryFrame(img, nullptr, bufferSize, 0, whiteBalance[0],
whiteBalance[1], whiteBalance[2],
exposureCompensation, timestamp);
ALOGVV("NV21 sensor image captured");
}
}; // end of namespace android