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 <log/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
QemuQuery()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
QemuQuery(const char * query_string)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
QemuQuery(const char * query_name,const char * query_param)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
~QemuQuery()74 QemuQuery::~QemuQuery() { QemuQuery::resetQuery(); }
75
createQuery(const char * name,const char * param)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
completeQuery(status_t status)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
resetQuery()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
QemuClient()188 QemuClient::QemuClient() : mPipeFD(-1) {}
189
~QemuClient()190 QemuClient::~QemuClient() {
191 if (mPipeFD >= 0) {
192 close(mPipeFD);
193 }
194 }
195
196 /****************************************************************************
197 * Qemu client API
198 ***************************************************************************/
199
connectClient(const char * param)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
disconnectClient()234 void QemuClient::disconnectClient() {
235 ALOGV("%s", __FUNCTION__);
236
237 if (mPipeFD >= 0) {
238 close(mPipeFD);
239 mPipeFD = -1;
240 }
241 }
242
sendMessage(const void * data,size_t data_size)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
receiveMessage(void ** data,size_t * data_size)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
doQuery(QemuQuery * query)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
FactoryQemuClient()360 FactoryQemuClient::FactoryQemuClient() : QemuClient() {}
361
~FactoryQemuClient()362 FactoryQemuClient::~FactoryQemuClient() {}
363
listCameras(char ** list)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
CameraQemuClient()412 CameraQemuClient::CameraQemuClient() : QemuClient() {}
413
~CameraQemuClient()414 CameraQemuClient::~CameraQemuClient() {}
415
queryConnect()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
queryDisconnect()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
queryStart(uint32_t pixel_format,int width,int height)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
queryStop()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
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)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