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