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