You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
611 lines
19 KiB
611 lines
19 KiB
/*
|
|
* Copyright (C) 2011 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.
|
|
*/
|
|
|
|
/*
|
|
* Contains implementation of classes that encapsulate connection to camera
|
|
* services in the emulator via qemu pipe.
|
|
*/
|
|
|
|
#define LOG_NDEBUG 1
|
|
#define LOG_TAG "EmulatedCamera_QemuClient"
|
|
#include <inttypes.h>
|
|
#include <log/log.h>
|
|
#include "EmulatedCamera.h"
|
|
#include "QemuClient.h"
|
|
|
|
#define LOG_QUERIES 0
|
|
#if LOG_QUERIES
|
|
#define LOGQ(...) ALOGD(__VA_ARGS__)
|
|
#else
|
|
#define LOGQ(...) (void(0))
|
|
|
|
#endif // LOG_QUERIES
|
|
|
|
#define QEMU_PIPE_DEBUG LOGQ
|
|
#include <qemud.h>
|
|
#include <qemu_pipe_bp.h>
|
|
|
|
namespace android {
|
|
|
|
/****************************************************************************
|
|
* Qemu query
|
|
***************************************************************************/
|
|
|
|
QemuQuery::QemuQuery()
|
|
: mQuery(mQueryPrealloc),
|
|
mQueryDeliveryStatus(NO_ERROR),
|
|
mReplyBuffer(NULL),
|
|
mReplyData(NULL),
|
|
mReplySize(0),
|
|
mReplyDataSize(0),
|
|
mReplyStatus(0)
|
|
{
|
|
*mQuery = '\0';
|
|
}
|
|
|
|
QemuQuery::QemuQuery(const char* query_string)
|
|
: mQuery(mQueryPrealloc),
|
|
mQueryDeliveryStatus(NO_ERROR),
|
|
mReplyBuffer(NULL),
|
|
mReplyData(NULL),
|
|
mReplySize(0),
|
|
mReplyDataSize(0),
|
|
mReplyStatus(0)
|
|
{
|
|
mQueryDeliveryStatus = QemuQuery::createQuery(query_string, NULL);
|
|
}
|
|
|
|
QemuQuery::QemuQuery(const char* query_name, const char* query_param)
|
|
: mQuery(mQueryPrealloc),
|
|
mQueryDeliveryStatus(NO_ERROR),
|
|
mReplyBuffer(NULL),
|
|
mReplyData(NULL),
|
|
mReplySize(0),
|
|
mReplyDataSize(0),
|
|
mReplyStatus(0)
|
|
{
|
|
mQueryDeliveryStatus = QemuQuery::createQuery(query_name, query_param);
|
|
}
|
|
|
|
QemuQuery::~QemuQuery()
|
|
{
|
|
QemuQuery::resetQuery();
|
|
}
|
|
|
|
status_t QemuQuery::createQuery(const char* name, const char* param)
|
|
{
|
|
/* Reset from the previous use. */
|
|
resetQuery();
|
|
|
|
/* Query name cannot be NULL or an empty string. */
|
|
if (name == NULL || *name == '\0') {
|
|
ALOGE("%s: NULL or an empty string is passed as query name.",
|
|
__FUNCTION__);
|
|
mQueryDeliveryStatus = EINVAL;
|
|
return EINVAL;
|
|
}
|
|
|
|
const size_t name_len = strlen(name);
|
|
const size_t param_len = (param != NULL) ? strlen(param) : 0;
|
|
const size_t required = strlen(name) + (param_len ? (param_len + 2) : 1);
|
|
|
|
if (required > sizeof(mQueryPrealloc)) {
|
|
/* Preallocated buffer was too small. Allocate a bigger query buffer. */
|
|
mQuery = new char[required];
|
|
if (mQuery == NULL) {
|
|
ALOGE("%s: Unable to allocate %zu bytes for query buffer",
|
|
__FUNCTION__, required);
|
|
mQueryDeliveryStatus = ENOMEM;
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
|
|
/* At this point mQuery buffer is big enough for the query. */
|
|
if (param_len) {
|
|
sprintf(mQuery, "%s %s", name, param);
|
|
} else {
|
|
memcpy(mQuery, name, name_len + 1);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t QemuQuery::completeQuery(status_t status)
|
|
{
|
|
/* Save query completion status. */
|
|
mQueryDeliveryStatus = status;
|
|
if (mQueryDeliveryStatus != NO_ERROR) {
|
|
return mQueryDeliveryStatus;
|
|
}
|
|
|
|
/* Make sure reply buffer contains at least 'ok', or 'ko'.
|
|
* Note that 'ok', or 'ko' prefixes are always 3 characters long: in case
|
|
* there are more data in the reply, that data will be separated from 'ok'/'ko'
|
|
* with a ':'. If there is no more data in the reply, the prefix will be
|
|
* zero-terminated, and the terminator will be inculded in the reply. */
|
|
if (mReplyBuffer == NULL || mReplySize < 3) {
|
|
ALOGE("%s: Invalid reply to the query", __FUNCTION__);
|
|
mQueryDeliveryStatus = EINVAL;
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Lets see the reply status. */
|
|
if (!memcmp(mReplyBuffer, "ok", 2)) {
|
|
mReplyStatus = 1;
|
|
} else if (!memcmp(mReplyBuffer, "ko", 2)) {
|
|
mReplyStatus = 0;
|
|
} else {
|
|
ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer);
|
|
mQueryDeliveryStatus = EINVAL;
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Lets see if there are reply data that follow. */
|
|
if (mReplySize > 3) {
|
|
/* There are extra data. Make sure they are separated from the status
|
|
* with a ':' */
|
|
if (mReplyBuffer[2] != ':') {
|
|
ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer);
|
|
mQueryDeliveryStatus = EINVAL;
|
|
return EINVAL;
|
|
}
|
|
mReplyData = mReplyBuffer + 3;
|
|
mReplyDataSize = mReplySize - 3;
|
|
} else {
|
|
/* Make sure reply buffer containing just 'ok'/'ko' ends with
|
|
* zero-terminator. */
|
|
if (mReplyBuffer[2] != '\0') {
|
|
ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer);
|
|
mQueryDeliveryStatus = EINVAL;
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void QemuQuery::resetQuery()
|
|
{
|
|
if (mQuery != NULL && mQuery != mQueryPrealloc) {
|
|
delete[] mQuery;
|
|
}
|
|
mQuery = mQueryPrealloc;
|
|
mQueryDeliveryStatus = NO_ERROR;
|
|
if (mReplyBuffer != NULL) {
|
|
free(mReplyBuffer);
|
|
mReplyBuffer = NULL;
|
|
}
|
|
mReplyData = NULL;
|
|
mReplySize = mReplyDataSize = 0;
|
|
mReplyStatus = 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Qemu client base
|
|
***************************************************************************/
|
|
|
|
/* Camera service name. */
|
|
const char QemuClient::mCameraServiceName[] = "camera";
|
|
|
|
QemuClient::QemuClient()
|
|
: mPipeFD(-1)
|
|
{
|
|
}
|
|
|
|
QemuClient::~QemuClient()
|
|
{
|
|
if (mPipeFD >= 0) {
|
|
close(mPipeFD);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Qemu client API
|
|
***************************************************************************/
|
|
|
|
status_t QemuClient::connectClient(const char* param)
|
|
{
|
|
ALOGV("%s: '%s'", __FUNCTION__, param ? param : "");
|
|
|
|
/* Make sure that client is not connected already. */
|
|
if (mPipeFD >= 0) {
|
|
ALOGE("%s: Qemu client is already connected", __FUNCTION__);
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Select one of the two: 'factory', or 'emulated camera' service */
|
|
if (param == NULL || *param == '\0') {
|
|
mPipeFD = qemud_channel_open(mCameraServiceName);
|
|
} else {
|
|
char buf[256];
|
|
snprintf(buf, sizeof(buf), "%s:%s", mCameraServiceName, param);
|
|
mPipeFD = qemud_channel_open(buf);
|
|
}
|
|
if (mPipeFD < 0) {
|
|
ALOGE("%s: Unable to connect to the camera service '%s': %s",
|
|
__FUNCTION__, param ? param : "Factory", strerror(errno));
|
|
return errno ? errno : EINVAL;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void QemuClient::disconnectClient()
|
|
{
|
|
ALOGV("%s", __FUNCTION__);
|
|
|
|
if (mPipeFD >= 0) {
|
|
close(mPipeFD);
|
|
mPipeFD = -1;
|
|
}
|
|
}
|
|
|
|
status_t QemuClient::sendMessage(const void* data, size_t data_size)
|
|
{
|
|
if (mPipeFD < 0) {
|
|
ALOGE("%s: Qemu client is not connected", __FUNCTION__);
|
|
return EINVAL;
|
|
}
|
|
|
|
if (qemu_pipe_write_fully(mPipeFD, data, data_size)) {
|
|
ALOGE("%s: Error sending data via qemu pipe: '%s'",
|
|
__FUNCTION__, strerror(errno));
|
|
return errno ? errno : EIO;
|
|
} else {
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
status_t QemuClient::receiveMessage(void** data, size_t* data_size)
|
|
{
|
|
*data = NULL;
|
|
*data_size = 0;
|
|
|
|
if (mPipeFD < 0) {
|
|
ALOGE("%s: Qemu client is not connected", __FUNCTION__);
|
|
return EINVAL;
|
|
}
|
|
|
|
/* The way the service replies to a query, it sends payload size first, and
|
|
* then it sends the payload itself. Note that payload size is sent as a
|
|
* string, containing 8 characters representing a hexadecimal payload size
|
|
* value. Note also, that the string doesn't contain zero-terminator. */
|
|
size_t payload_size;
|
|
char payload_size_str[9];
|
|
if (qemu_pipe_read_fully(mPipeFD, payload_size_str, 8)) {
|
|
ALOGE("%s: Unable to obtain payload size: %s",
|
|
__FUNCTION__, strerror(errno));
|
|
return errno ? errno : EIO;
|
|
}
|
|
|
|
/* Convert payload size. */
|
|
errno = 0;
|
|
payload_size_str[8] = '\0';
|
|
payload_size = strtol(payload_size_str, NULL, 16);
|
|
if (errno) {
|
|
ALOGE("%s: Invalid payload size '%s'", __FUNCTION__, payload_size_str);
|
|
return EIO;
|
|
}
|
|
|
|
/* Allocate payload data buffer, and read the payload there. */
|
|
*data = malloc(payload_size);
|
|
if (*data == NULL) {
|
|
ALOGE("%s: Unable to allocate %zu bytes payload buffer",
|
|
__FUNCTION__, payload_size);
|
|
return ENOMEM;
|
|
}
|
|
if (qemu_pipe_read_fully(mPipeFD, *data, payload_size)) {
|
|
ALOGE("%s: qemu_pipe_read_fully coud not read %zu bytes: %s",
|
|
__FUNCTION__, payload_size, strerror(errno));
|
|
free(*data);
|
|
*data = NULL;
|
|
return errno ? errno : EIO;
|
|
} else {
|
|
*data_size = payload_size;
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
status_t QemuClient::doQuery(QemuQuery* query)
|
|
{
|
|
/* Make sure that query has been successfuly constructed. */
|
|
if (query->mQueryDeliveryStatus != NO_ERROR) {
|
|
ALOGE("%s: Query is invalid", __FUNCTION__);
|
|
return query->mQueryDeliveryStatus;
|
|
}
|
|
|
|
LOGQ("Send query '%s'", query->mQuery);
|
|
|
|
/* Send the query. */
|
|
status_t res = sendMessage(query->mQuery, strlen(query->mQuery) + 1);
|
|
if (res == NO_ERROR) {
|
|
/* Read the response. */
|
|
res = receiveMessage(reinterpret_cast<void**>(&query->mReplyBuffer),
|
|
&query->mReplySize);
|
|
if (res == NO_ERROR) {
|
|
LOGQ("Response to query '%s': Status = '%.2s', %d bytes in response",
|
|
query->mQuery, query->mReplyBuffer, query->mReplySize);
|
|
} else {
|
|
ALOGE("%s Response to query '%s' has failed: %s",
|
|
__FUNCTION__, query->mQuery, strerror(res));
|
|
}
|
|
} else {
|
|
ALOGE("%s: Send query '%s' failed: %s",
|
|
__FUNCTION__, query->mQuery, strerror(res));
|
|
}
|
|
|
|
/* Complete the query, and return its completion handling status. */
|
|
const status_t res1 = query->completeQuery(res);
|
|
ALOGE_IF(res1 != NO_ERROR && res1 != res,
|
|
"%s: Error %d in query '%s' completion",
|
|
__FUNCTION__, res1, query->mQuery);
|
|
return res1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Qemu client for the 'factory' service.
|
|
***************************************************************************/
|
|
|
|
/*
|
|
* Factory service queries.
|
|
*/
|
|
|
|
/* Queries list of cameras connected to the host. */
|
|
const char FactoryQemuClient::mQueryList[] = "list";
|
|
|
|
FactoryQemuClient::FactoryQemuClient()
|
|
: QemuClient()
|
|
{
|
|
}
|
|
|
|
FactoryQemuClient::~FactoryQemuClient()
|
|
{
|
|
}
|
|
|
|
status_t FactoryQemuClient::listCameras(char** list)
|
|
{
|
|
ALOGV("%s", __FUNCTION__);
|
|
|
|
QemuQuery query(mQueryList);
|
|
if (doQuery(&query) || !query.isQuerySucceeded()) {
|
|
ALOGE("%s: List cameras query failed: %s", __FUNCTION__,
|
|
query.mReplyData ? query.mReplyData : "No error message");
|
|
return query.getCompletionStatus();
|
|
}
|
|
|
|
/* Make sure there is a list returned. */
|
|
if (query.mReplyDataSize == 0) {
|
|
ALOGE("%s: No camera list is returned.", __FUNCTION__);
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Copy the list over. */
|
|
*list = (char*)malloc(query.mReplyDataSize);
|
|
if (*list != NULL) {
|
|
memcpy(*list, query.mReplyData, query.mReplyDataSize);
|
|
ALOGD("Emulated camera list: %s", *list);
|
|
return NO_ERROR;
|
|
} else {
|
|
ALOGE("%s: Unable to allocate %zu bytes",
|
|
__FUNCTION__, query.mReplyDataSize);
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Qemu client for an 'emulated camera' service.
|
|
***************************************************************************/
|
|
|
|
/*
|
|
* Emulated camera queries
|
|
*/
|
|
|
|
/* Connect to the camera device. */
|
|
const char CameraQemuClient::mQueryConnect[] = "connect";
|
|
/* Disconect from the camera device. */
|
|
const char CameraQemuClient::mQueryDisconnect[] = "disconnect";
|
|
/* Start capturing video from the camera device. */
|
|
const char CameraQemuClient::mQueryStart[] = "start";
|
|
/* Stop capturing video from the camera device. */
|
|
const char CameraQemuClient::mQueryStop[] = "stop";
|
|
/* Get next video frame from the camera device. */
|
|
const char CameraQemuClient::mQueryFrame[] = "frame";
|
|
|
|
CameraQemuClient::CameraQemuClient()
|
|
: QemuClient()
|
|
{
|
|
}
|
|
|
|
CameraQemuClient::~CameraQemuClient()
|
|
{
|
|
|
|
}
|
|
|
|
status_t CameraQemuClient::queryConnect()
|
|
{
|
|
ALOGV("%s", __FUNCTION__);
|
|
|
|
QemuQuery query(mQueryConnect);
|
|
doQuery(&query);
|
|
const status_t res = query.getCompletionStatus();
|
|
ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
|
|
__FUNCTION__, query.mReplyData ? query.mReplyData :
|
|
"No error message");
|
|
return res;
|
|
}
|
|
|
|
status_t CameraQemuClient::queryDisconnect()
|
|
{
|
|
ALOGV("%s", __FUNCTION__);
|
|
|
|
QemuQuery query(mQueryDisconnect);
|
|
doQuery(&query);
|
|
const status_t res = query.getCompletionStatus();
|
|
ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
|
|
__FUNCTION__, query.mReplyData ? query.mReplyData :
|
|
"No error message");
|
|
return res;
|
|
}
|
|
|
|
status_t CameraQemuClient::queryStart() {
|
|
ALOGV("%s", __FUNCTION__);
|
|
QemuQuery query(mQueryStart);
|
|
doQuery(&query);
|
|
const status_t res = query.getCompletionStatus();
|
|
ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
|
|
__FUNCTION__, query.mReplyData ? query.mReplyData :
|
|
"No error message");
|
|
return res;
|
|
}
|
|
|
|
status_t CameraQemuClient::queryStart(uint32_t pixel_format,
|
|
int width,
|
|
int height)
|
|
{
|
|
ALOGV("%s", __FUNCTION__);
|
|
|
|
char query_str[256];
|
|
snprintf(query_str, sizeof(query_str), "%s dim=%dx%d pix=%d",
|
|
mQueryStart, width, height, pixel_format);
|
|
QemuQuery query(query_str);
|
|
doQuery(&query);
|
|
const status_t res = query.getCompletionStatus();
|
|
ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
|
|
__FUNCTION__, query.mReplyData ? query.mReplyData :
|
|
"No error message");
|
|
return res;
|
|
}
|
|
|
|
status_t CameraQemuClient::queryStop()
|
|
{
|
|
ALOGV("%s", __FUNCTION__);
|
|
|
|
QemuQuery query(mQueryStop);
|
|
doQuery(&query);
|
|
const status_t res = query.getCompletionStatus();
|
|
ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
|
|
__FUNCTION__, query.mReplyData ? query.mReplyData :
|
|
"No error message");
|
|
return res;
|
|
}
|
|
|
|
status_t CameraQemuClient::queryFrame(void* vframe,
|
|
void* pframe,
|
|
size_t vframe_size,
|
|
size_t pframe_size,
|
|
float r_scale,
|
|
float g_scale,
|
|
float b_scale,
|
|
float exposure_comp,
|
|
int64_t* frame_time)
|
|
{
|
|
ALOGV("%s", __FUNCTION__);
|
|
|
|
char query_str[256];
|
|
snprintf(query_str, sizeof(query_str), "%s video=%zu preview=%zu whiteb=%g,%g,%g expcomp=%g time=%d",
|
|
mQueryFrame, (vframe && vframe_size) ? vframe_size : 0,
|
|
(pframe && pframe_size) ? pframe_size : 0, r_scale, g_scale, b_scale,
|
|
exposure_comp, frame_time != nullptr ? 1 : 0);
|
|
QemuQuery query(query_str);
|
|
doQuery(&query);
|
|
const status_t res = query.getCompletionStatus();
|
|
if( res != NO_ERROR) {
|
|
ALOGE("%s: Query failed: %s",
|
|
__FUNCTION__, query.mReplyData ? query.mReplyData :
|
|
"No error message");
|
|
return res;
|
|
}
|
|
|
|
/* Copy requested frames. */
|
|
size_t cur_offset = 0;
|
|
const uint8_t* frame = reinterpret_cast<const uint8_t*>(query.mReplyData);
|
|
/* Video frame is always first. */
|
|
if (vframe != NULL && vframe_size != 0) {
|
|
/* Make sure that video frame is in. */
|
|
if ((query.mReplyDataSize - cur_offset) >= vframe_size) {
|
|
memcpy(vframe, frame, vframe_size);
|
|
cur_offset += vframe_size;
|
|
} else {
|
|
ALOGE("%s: Reply %zu bytes is to small to contain %zu bytes video frame",
|
|
__FUNCTION__, query.mReplyDataSize - cur_offset, vframe_size);
|
|
return EINVAL;
|
|
}
|
|
}
|
|
if (pframe != NULL && pframe_size != 0) {
|
|
/* Make sure that preview frame is in. */
|
|
if ((query.mReplyDataSize - cur_offset) >= pframe_size) {
|
|
memcpy(pframe, frame + cur_offset, pframe_size);
|
|
cur_offset += pframe_size;
|
|
} else {
|
|
ALOGE("%s: Reply %zu bytes is to small to contain %zu bytes preview frame",
|
|
__FUNCTION__, query.mReplyDataSize - cur_offset, pframe_size);
|
|
return EINVAL;
|
|
}
|
|
}
|
|
if (frame_time != nullptr) {
|
|
if (query.mReplyDataSize - cur_offset >= 8) {
|
|
*frame_time = *reinterpret_cast<const int64_t*>(frame + cur_offset);
|
|
cur_offset += 8;
|
|
} else {
|
|
*frame_time = 0L;
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t CameraQemuClient::queryFrame(int width,
|
|
int height,
|
|
uint32_t pixel_format,
|
|
uint64_t offset,
|
|
float r_scale,
|
|
float g_scale,
|
|
float b_scale,
|
|
float exposure_comp,
|
|
int64_t* frame_time)
|
|
{
|
|
ALOGV("%s: w %d h %d %.4s offset 0x%" PRIu64 "", __FUNCTION__, width, height,
|
|
(char*)(&pixel_format), offset);
|
|
|
|
char query_str[256];
|
|
snprintf(query_str, sizeof(query_str), "%s dim=%dx%d pix=%d offset=%" PRIu64 " whiteb=%g,%g,%g expcomp=%g time=%d",
|
|
mQueryFrame, width, height, pixel_format, offset,
|
|
r_scale, g_scale, b_scale,
|
|
exposure_comp, frame_time != nullptr ? 1 : 0);
|
|
QemuQuery query(query_str);
|
|
doQuery(&query);
|
|
const status_t res = query.getCompletionStatus();
|
|
if( res != NO_ERROR) {
|
|
ALOGE("%s: Query failed: %s",
|
|
__FUNCTION__, query.mReplyData ? query.mReplyData :
|
|
"No error message");
|
|
return res;
|
|
}
|
|
|
|
/* Copy requested frames. */
|
|
const uint8_t* frame = reinterpret_cast<const uint8_t*>(query.mReplyData);
|
|
if (frame_time != nullptr) {
|
|
if (query.mReplyDataSize >= 8) {
|
|
*frame_time = *reinterpret_cast<const int64_t*>(frame);
|
|
} else {
|
|
*frame_time = 0L;
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
}; /* namespace android */
|