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