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
QemuQuery()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
QemuQuery(const char * query_string)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
QemuQuery(const char * query_name,const char * query_param)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
~QemuQuery()81 QemuQuery::~QemuQuery()
82 {
83 QemuQuery::resetQuery();
84 }
85
createQuery(const char * name,const char * param)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
completeQuery(status_t status)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
resetQuery()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
QemuClient()201 QemuClient::QemuClient()
202 : mPipeFD(-1)
203 {
204 }
205
~QemuClient()206 QemuClient::~QemuClient()
207 {
208 if (mPipeFD >= 0) {
209 close(mPipeFD);
210 }
211 }
212
213 /****************************************************************************
214 * Qemu client API
215 ***************************************************************************/
216
connectClient(const char * param)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
disconnectClient()253 void QemuClient::disconnectClient()
254 {
255 ALOGV("%s", __FUNCTION__);
256
257 if (mPipeFD >= 0) {
258 close(mPipeFD);
259 mPipeFD = -1;
260 }
261 }
262
sendMessage(const void * data,size_t data_size)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
receiveMessage(void ** data,size_t * data_size)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
doQuery(QemuQuery * query)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
FactoryQemuClient()379 FactoryQemuClient::FactoryQemuClient()
380 : QemuClient()
381 {
382 }
383
~FactoryQemuClient()384 FactoryQemuClient::~FactoryQemuClient()
385 {
386 }
387
listCameras(char ** list)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
CameraQemuClient()437 CameraQemuClient::CameraQemuClient()
438 : QemuClient()
439 {
440 }
441
~CameraQemuClient()442 CameraQemuClient::~CameraQemuClient()
443 {
444
445 }
446
queryConnect()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
queryDisconnect()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
queryStart(uint32_t pixel_format,int width,int height)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
queryStop()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
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)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