1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /* 18 * Contains implementation of classes that encapsulate connection to camera 19 * services in the emulator via qemu pipe. 20 */ 21 22 #define LOG_NDEBUG 0 23 #define LOG_TAG "EmulatedCamera_QemuClient" 24 #include "QemuClient.h" 25 #include <cutils/log.h> 26 #include "EmulatedCamera.h" 27 28 #define LOG_QUERIES 0 29 #if LOG_QUERIES 30 #define LOGQ(...) ALOGD(__VA_ARGS__) 31 #else 32 #define LOGQ(...) (void(0)) 33 34 #endif // LOG_QUERIES 35 namespace android { 36 37 /**************************************************************************** 38 * Qemu query 39 ***************************************************************************/ 40 41 QemuQuery::QemuQuery() 42 : mQuery(mQueryPrealloc), 43 mQueryDeliveryStatus(NO_ERROR), 44 mReplyBuffer(NULL), 45 mReplyData(NULL), 46 mReplySize(0), 47 mReplyDataSize(0), 48 mReplyStatus(0) { 49 *mQuery = '\0'; 50 } 51 52 QemuQuery::QemuQuery(const char* query_string) 53 : mQuery(mQueryPrealloc), 54 mQueryDeliveryStatus(NO_ERROR), 55 mReplyBuffer(NULL), 56 mReplyData(NULL), 57 mReplySize(0), 58 mReplyDataSize(0), 59 mReplyStatus(0) { 60 mQueryDeliveryStatus = QemuQuery::createQuery(query_string, NULL); 61 } 62 63 QemuQuery::QemuQuery(const char* query_name, const char* query_param) 64 : mQuery(mQueryPrealloc), 65 mQueryDeliveryStatus(NO_ERROR), 66 mReplyBuffer(NULL), 67 mReplyData(NULL), 68 mReplySize(0), 69 mReplyDataSize(0), 70 mReplyStatus(0) { 71 mQueryDeliveryStatus = QemuQuery::createQuery(query_name, query_param); 72 } 73 74 QemuQuery::~QemuQuery() { QemuQuery::resetQuery(); } 75 76 status_t QemuQuery::createQuery(const char* name, const char* param) { 77 /* Reset from the previous use. */ 78 resetQuery(); 79 80 /* Query name cannot be NULL or an empty string. */ 81 if (name == NULL || *name == '\0') { 82 ALOGE("%s: NULL or an empty string is passed as query name.", __FUNCTION__); 83 mQueryDeliveryStatus = EINVAL; 84 return EINVAL; 85 } 86 87 const size_t name_len = strlen(name); 88 const size_t param_len = (param != NULL) ? strlen(param) : 0; 89 const size_t required = strlen(name) + (param_len ? (param_len + 2) : 1); 90 91 if (required > sizeof(mQueryPrealloc)) { 92 /* Preallocated buffer was too small. Allocate a bigger query buffer. */ 93 mQuery = new char[required]; 94 if (mQuery == NULL) { 95 ALOGE("%s: Unable to allocate %zu bytes for query buffer", __FUNCTION__, 96 required); 97 mQueryDeliveryStatus = ENOMEM; 98 return ENOMEM; 99 } 100 } 101 102 /* At this point mQuery buffer is big enough for the query. */ 103 if (param_len) { 104 sprintf(mQuery, "%s %s", name, param); 105 } else { 106 memcpy(mQuery, name, name_len + 1); 107 } 108 109 return NO_ERROR; 110 } 111 112 status_t QemuQuery::completeQuery(status_t status) { 113 /* Save query completion status. */ 114 mQueryDeliveryStatus = status; 115 if (mQueryDeliveryStatus != NO_ERROR) { 116 return mQueryDeliveryStatus; 117 } 118 119 /* Make sure reply buffer contains at least 'ok', or 'ko'. 120 * Note that 'ok', or 'ko' prefixes are always 3 characters long: in case 121 * there are more data in the reply, that data will be separated from 122 * 'ok'/'ko' with a ':'. If there is no more data in the reply, the prefix 123 * will be 124 * zero-terminated, and the terminator will be inculded in the reply. */ 125 if (mReplyBuffer == NULL || mReplySize < 3) { 126 ALOGE("%s: Invalid reply to the query", __FUNCTION__); 127 mQueryDeliveryStatus = EINVAL; 128 return EINVAL; 129 } 130 131 /* Lets see the reply status. */ 132 if (!memcmp(mReplyBuffer, "ok", 2)) { 133 mReplyStatus = 1; 134 } else if (!memcmp(mReplyBuffer, "ko", 2)) { 135 mReplyStatus = 0; 136 } else { 137 ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer); 138 mQueryDeliveryStatus = EINVAL; 139 return EINVAL; 140 } 141 142 /* Lets see if there are reply data that follow. */ 143 if (mReplySize > 3) { 144 /* There are extra data. Make sure they are separated from the status 145 * with a ':' */ 146 if (mReplyBuffer[2] != ':') { 147 ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer); 148 mQueryDeliveryStatus = EINVAL; 149 return EINVAL; 150 } 151 mReplyData = mReplyBuffer + 3; 152 mReplyDataSize = mReplySize - 3; 153 } else { 154 /* Make sure reply buffer containing just 'ok'/'ko' ends with 155 * zero-terminator. */ 156 if (mReplyBuffer[2] != '\0') { 157 ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer); 158 mQueryDeliveryStatus = EINVAL; 159 return EINVAL; 160 } 161 } 162 163 return NO_ERROR; 164 } 165 166 void QemuQuery::resetQuery() { 167 if (mQuery != NULL && mQuery != mQueryPrealloc) { 168 delete[] mQuery; 169 } 170 mQuery = mQueryPrealloc; 171 mQueryDeliveryStatus = NO_ERROR; 172 if (mReplyBuffer != NULL) { 173 free(mReplyBuffer); 174 mReplyBuffer = NULL; 175 } 176 mReplyData = NULL; 177 mReplySize = mReplyDataSize = 0; 178 mReplyStatus = 0; 179 } 180 181 /**************************************************************************** 182 * Qemu client base 183 ***************************************************************************/ 184 185 /* Camera service name. */ 186 const char QemuClient::mCameraServiceName[] = "camera"; 187 188 QemuClient::QemuClient() : mPipeFD(-1) {} 189 190 QemuClient::~QemuClient() { 191 if (mPipeFD >= 0) { 192 close(mPipeFD); 193 } 194 } 195 196 /**************************************************************************** 197 * Qemu client API 198 ***************************************************************************/ 199 200 status_t QemuClient::connectClient(const char* param) { 201 ALOGV("%s: '%s'", __FUNCTION__, param ? param : ""); 202 203 /* Make sure that client is not connected already. */ 204 if (mPipeFD >= 0) { 205 ALOGE("%s: Qemu client is already connected", __FUNCTION__); 206 return EINVAL; 207 } 208 209 /* Select one of the two: 'factory', or 'emulated camera' service */ 210 if (param == NULL || *param == '\0') { 211 /* No parameters: connect to the factory service. */ 212 char pipe_name[512]; 213 snprintf(pipe_name, sizeof(pipe_name), "qemud:%s", mCameraServiceName); 214 mPipeFD = qemu_pipe_open(pipe_name); 215 } else { 216 /* One extra char ':' that separates service name and parameters + six 217 * characters for 'qemud:'. This is required by qemu pipe protocol. */ 218 char* connection_str = 219 new char[strlen(mCameraServiceName) + strlen(param) + 8]; 220 sprintf(connection_str, "qemud:%s:%s", mCameraServiceName, param); 221 222 mPipeFD = qemu_pipe_open(connection_str); 223 delete[] connection_str; 224 } 225 if (mPipeFD < 0) { 226 ALOGE("%s: Unable to connect to the camera service '%s': %s", __FUNCTION__, 227 param ? param : "Factory", strerror(errno)); 228 return errno ? errno : EINVAL; 229 } 230 231 return NO_ERROR; 232 } 233 234 void QemuClient::disconnectClient() { 235 ALOGV("%s", __FUNCTION__); 236 237 if (mPipeFD >= 0) { 238 close(mPipeFD); 239 mPipeFD = -1; 240 } 241 } 242 243 status_t QemuClient::sendMessage(const void* data, size_t data_size) { 244 if (mPipeFD < 0) { 245 ALOGE("%s: Qemu client is not connected", __FUNCTION__); 246 return EINVAL; 247 } 248 249 /* Note that we don't use here qemud_client_send, since with qemu pipes we 250 * don't need to provide payload size prior to payload when we're writing to 251 * the pipe. So, we can use simple write, and qemu pipe will take care of the 252 * rest, calling the receiving end with the number of bytes transferred. */ 253 const size_t written = qemud_fd_write(mPipeFD, data, data_size); 254 if (written == data_size) { 255 return NO_ERROR; 256 } else { 257 ALOGE("%s: Error sending data via qemu pipe: '%s'", __FUNCTION__, 258 strerror(errno)); 259 return errno ? errno : EIO; 260 } 261 } 262 263 status_t QemuClient::receiveMessage(void** data, size_t* data_size) { 264 *data = NULL; 265 *data_size = 0; 266 267 if (mPipeFD < 0) { 268 ALOGE("%s: Qemu client is not connected", __FUNCTION__); 269 return EINVAL; 270 } 271 272 /* The way the service replies to a query, it sends payload size first, and 273 * then it sends the payload itself. Note that payload size is sent as a 274 * string, containing 8 characters representing a hexadecimal payload size 275 * value. Note also, that the string doesn't contain zero-terminator. */ 276 size_t payload_size; 277 char payload_size_str[9]; 278 int rd_res = qemud_fd_read(mPipeFD, payload_size_str, 8); 279 if (rd_res != 8) { 280 ALOGE("%s: Unable to obtain payload size: %s", __FUNCTION__, 281 strerror(errno)); 282 return errno ? errno : EIO; 283 } 284 285 /* Convert payload size. */ 286 errno = 0; 287 payload_size_str[8] = '\0'; 288 payload_size = strtol(payload_size_str, NULL, 16); 289 if (errno) { 290 ALOGE("%s: Invalid payload size '%s'", __FUNCTION__, payload_size_str); 291 return EIO; 292 } 293 294 /* Allocate payload data buffer, and read the payload there. */ 295 *data = malloc(payload_size); 296 if (*data == NULL) { 297 ALOGE("%s: Unable to allocate %zu bytes payload buffer", __FUNCTION__, 298 payload_size); 299 return ENOMEM; 300 } 301 rd_res = qemud_fd_read(mPipeFD, *data, payload_size); 302 if (static_cast<size_t>(rd_res) == payload_size) { 303 *data_size = payload_size; 304 return NO_ERROR; 305 } else { 306 ALOGE("%s: Read size %d doesnt match expected payload size %zu: %s", 307 __FUNCTION__, rd_res, payload_size, strerror(errno)); 308 free(*data); 309 *data = NULL; 310 return errno ? errno : EIO; 311 } 312 } 313 314 status_t QemuClient::doQuery(QemuQuery* query) { 315 /* Make sure that query has been successfuly constructed. */ 316 if (query->mQueryDeliveryStatus != NO_ERROR) { 317 ALOGE("%s: Query is invalid", __FUNCTION__); 318 return query->mQueryDeliveryStatus; 319 } 320 321 LOGQ("Send query '%s'", query->mQuery); 322 323 /* Send the query. */ 324 status_t res = sendMessage(query->mQuery, strlen(query->mQuery) + 1); 325 if (res == NO_ERROR) { 326 /* Read the response. */ 327 res = receiveMessage(reinterpret_cast<void**>(&query->mReplyBuffer), 328 &query->mReplySize); 329 if (res == NO_ERROR) { 330 LOGQ("Response to query '%s': Status = '%.2s', %d bytes in response", 331 query->mQuery, query->mReplyBuffer, query->mReplySize); 332 } else { 333 ALOGE("%s Response to query '%s' has failed: %s", __FUNCTION__, 334 query->mQuery, strerror(res)); 335 } 336 } else { 337 ALOGE("%s: Send query '%s' failed: %s", __FUNCTION__, query->mQuery, 338 strerror(res)); 339 } 340 341 /* Complete the query, and return its completion handling status. */ 342 const status_t res1 = query->completeQuery(res); 343 ALOGE_IF(res1 != NO_ERROR && res1 != res, 344 "%s: Error %d in query '%s' completion", __FUNCTION__, res1, 345 query->mQuery); 346 return res1; 347 } 348 349 /**************************************************************************** 350 * Qemu client for the 'factory' service. 351 ***************************************************************************/ 352 353 /* 354 * Factory service queries. 355 */ 356 357 /* Queries list of cameras connected to the host. */ 358 const char FactoryQemuClient::mQueryList[] = "list"; 359 360 FactoryQemuClient::FactoryQemuClient() : QemuClient() {} 361 362 FactoryQemuClient::~FactoryQemuClient() {} 363 364 status_t FactoryQemuClient::listCameras(char** list) { 365 ALOGV("%s", __FUNCTION__); 366 367 QemuQuery query(mQueryList); 368 if (doQuery(&query) || !query.isQuerySucceeded()) { 369 ALOGE("%s: List cameras query failed: %s", __FUNCTION__, 370 query.mReplyData ? query.mReplyData : "No error message"); 371 return query.getCompletionStatus(); 372 } 373 374 /* Make sure there is a list returned. */ 375 if (query.mReplyDataSize == 0) { 376 ALOGE("%s: No camera list is returned.", __FUNCTION__); 377 return EINVAL; 378 } 379 380 /* Copy the list over. */ 381 *list = (char*)malloc(query.mReplyDataSize); 382 if (*list != NULL) { 383 memcpy(*list, query.mReplyData, query.mReplyDataSize); 384 ALOGD("Emulated camera list: %s", *list); 385 return NO_ERROR; 386 } else { 387 ALOGE("%s: Unable to allocate %zu bytes", __FUNCTION__, 388 query.mReplyDataSize); 389 return ENOMEM; 390 } 391 } 392 393 /**************************************************************************** 394 * Qemu client for an 'emulated camera' service. 395 ***************************************************************************/ 396 397 /* 398 * Emulated camera queries 399 */ 400 401 /* Connect to the camera device. */ 402 const char CameraQemuClient::mQueryConnect[] = "connect"; 403 /* Disconect from the camera device. */ 404 const char CameraQemuClient::mQueryDisconnect[] = "disconnect"; 405 /* Start capturing video from the camera device. */ 406 const char CameraQemuClient::mQueryStart[] = "start"; 407 /* Stop capturing video from the camera device. */ 408 const char CameraQemuClient::mQueryStop[] = "stop"; 409 /* Get next video frame from the camera device. */ 410 const char CameraQemuClient::mQueryFrame[] = "frame"; 411 412 CameraQemuClient::CameraQemuClient() : QemuClient() {} 413 414 CameraQemuClient::~CameraQemuClient() {} 415 416 status_t CameraQemuClient::queryConnect() { 417 ALOGV("%s", __FUNCTION__); 418 419 QemuQuery query(mQueryConnect); 420 doQuery(&query); 421 const status_t res = query.getCompletionStatus(); 422 ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s", __FUNCTION__, 423 query.mReplyData ? query.mReplyData : "No error message"); 424 return res; 425 } 426 427 status_t CameraQemuClient::queryDisconnect() { 428 ALOGV("%s", __FUNCTION__); 429 430 QemuQuery query(mQueryDisconnect); 431 doQuery(&query); 432 const status_t res = query.getCompletionStatus(); 433 ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s", __FUNCTION__, 434 query.mReplyData ? query.mReplyData : "No error message"); 435 return res; 436 } 437 438 status_t CameraQemuClient::queryStart(uint32_t pixel_format, int width, 439 int height) { 440 ALOGV("%s", __FUNCTION__); 441 442 char query_str[256]; 443 snprintf(query_str, sizeof(query_str), "%s dim=%dx%d pix=%d", mQueryStart, 444 width, height, pixel_format); 445 QemuQuery query(query_str); 446 doQuery(&query); 447 const status_t res = query.getCompletionStatus(); 448 ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s", __FUNCTION__, 449 query.mReplyData ? query.mReplyData : "No error message"); 450 return res; 451 } 452 453 status_t CameraQemuClient::queryStop() { 454 ALOGV("%s", __FUNCTION__); 455 456 QemuQuery query(mQueryStop); 457 doQuery(&query); 458 const status_t res = query.getCompletionStatus(); 459 ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s", __FUNCTION__, 460 query.mReplyData ? query.mReplyData : "No error message"); 461 return res; 462 } 463 464 status_t CameraQemuClient::queryFrame(void* vframe, void* pframe, 465 size_t vframe_size, size_t pframe_size, 466 float r_scale, float g_scale, 467 float b_scale, float exposure_comp) { 468 ALOGV("%s", __FUNCTION__); 469 470 char query_str[256]; 471 snprintf(query_str, sizeof(query_str), 472 "%s video=%zu preview=%zu whiteb=%g,%g,%g expcomp=%g", mQueryFrame, 473 (vframe && vframe_size) ? vframe_size : 0, 474 (pframe && pframe_size) ? pframe_size : 0, r_scale, g_scale, b_scale, 475 exposure_comp); 476 QemuQuery query(query_str); 477 doQuery(&query); 478 const status_t res = query.getCompletionStatus(); 479 if (res != NO_ERROR) { 480 ALOGE("%s: Query failed: %s", __FUNCTION__, 481 query.mReplyData ? query.mReplyData : "No error message"); 482 return res; 483 } 484 485 /* Copy requested frames. */ 486 size_t cur_offset = 0; 487 const uint8_t* frame = reinterpret_cast<const uint8_t*>(query.mReplyData); 488 /* Video frame is always first. */ 489 if (vframe != NULL && vframe_size != 0) { 490 /* Make sure that video frame is in. */ 491 if ((query.mReplyDataSize - cur_offset) >= vframe_size) { 492 memcpy(vframe, frame, vframe_size); 493 cur_offset += vframe_size; 494 } else { 495 ALOGE("%s: Reply %zu bytes is to small to contain %zu bytes video frame", 496 __FUNCTION__, query.mReplyDataSize - cur_offset, vframe_size); 497 return EINVAL; 498 } 499 } 500 if (pframe != NULL && pframe_size != 0) { 501 /* Make sure that preview frame is in. */ 502 if ((query.mReplyDataSize - cur_offset) >= pframe_size) { 503 memcpy(pframe, frame + cur_offset, pframe_size); 504 cur_offset += pframe_size; 505 } else { 506 ALOGE( 507 "%s: Reply %zu bytes is to small to contain %zu bytes preview frame", 508 __FUNCTION__, query.mReplyDataSize - cur_offset, pframe_size); 509 return EINVAL; 510 } 511 } 512 513 return NO_ERROR; 514 } 515 516 }; /* namespace android */ 517