/* * 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 <cutils/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 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 %d 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') { /* No parameters: connect to the factory service. */ char pipe_name[512]; snprintf(pipe_name, sizeof(pipe_name), "qemud:%s", mCameraServiceName); mPipeFD = qemu_pipe_open(pipe_name); } else { /* One extra char ':' that separates service name and parameters + six * characters for 'qemud:'. This is required by qemu pipe protocol. */ char* connection_str = new char[strlen(mCameraServiceName) + strlen(param) + 8]; sprintf(connection_str, "qemud:%s:%s", mCameraServiceName, param); mPipeFD = qemu_pipe_open(connection_str); delete[] connection_str; } 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; } /* Note that we don't use here qemud_client_send, since with qemu pipes we * don't need to provide payload size prior to payload when we're writing to * the pipe. So, we can use simple write, and qemu pipe will take care of the * rest, calling the receiving end with the number of bytes transferred. */ const size_t written = qemud_fd_write(mPipeFD, data, data_size); if (written == data_size) { return NO_ERROR; } else { ALOGE("%s: Error sending data via qemu pipe: '%s'", __FUNCTION__, strerror(errno)); return errno ? errno : EIO; } } 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]; int rd_res = qemud_fd_read(mPipeFD, payload_size_str, 8); if (rd_res != 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 %d bytes payload buffer", __FUNCTION__, payload_size); return ENOMEM; } rd_res = qemud_fd_read(mPipeFD, *data, payload_size); if (static_cast<size_t>(rd_res) == payload_size) { *data_size = payload_size; return NO_ERROR; } else { ALOGE("%s: Read size %d doesnt match expected payload size %d: %s", __FUNCTION__, rd_res, payload_size, strerror(errno)); free(*data); *data = NULL; return errno ? errno : EIO; } } 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 %d 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(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) { ALOGV("%s", __FUNCTION__); char query_str[256]; snprintf(query_str, sizeof(query_str), "%s video=%d preview=%d whiteb=%g,%g,%g expcomp=%g", mQueryFrame, (vframe && vframe_size) ? vframe_size : 0, (pframe && pframe_size) ? pframe_size : 0, r_scale, g_scale, b_scale, exposure_comp); 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 %d bytes is to small to contain %d 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 %d bytes is to small to contain %d bytes preview frame", __FUNCTION__, query.mReplyDataSize - cur_offset, pframe_size); return EINVAL; } } return NO_ERROR; } }; /* namespace android */