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 |
35 | namespace android { |
36 | |
37 | /**************************************************************************** |
38 | * Qemu query |
39 | ***************************************************************************/ |
40 | |
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 | { |
50 | *mQuery = '\0'; |
51 | } |
52 | |
53 | QemuQuery::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 | |
65 | QemuQuery::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 | |
77 | QemuQuery::~QemuQuery() |
78 | { |
79 | QemuQuery::resetQuery(); |
80 | } |
81 | |
82 | status_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 | |
120 | status_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 | |
174 | void 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. */ |
195 | const char QemuClient::mCameraServiceName[] = "camera"; |
196 | |
197 | QemuClient::QemuClient() |
198 | : mPipeFD(-1) |
199 | { |
200 | } |
201 | |
202 | QemuClient::~QemuClient() |
203 | { |
204 | if (mPipeFD >= 0) { |
205 | close(mPipeFD); |
206 | } |
207 | } |
208 | |
209 | /**************************************************************************** |
210 | * Qemu client API |
211 | ***************************************************************************/ |
212 | |
213 | status_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 | |
248 | void QemuClient::disconnectClient() |
249 | { |
250 | ALOGV("%s", __FUNCTION__); |
251 | |
252 | if (mPipeFD >= 0) { |
253 | close(mPipeFD); |
254 | mPipeFD = -1; |
255 | } |
256 | } |
257 | |
258 | status_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 | |
279 | status_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 | |
331 | status_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. */ |
376 | const char FactoryQemuClient::mQueryList[] = "list"; |
377 | |
378 | FactoryQemuClient::FactoryQemuClient() |
379 | : QemuClient() |
380 | { |
381 | } |
382 | |
383 | FactoryQemuClient::~FactoryQemuClient() |
384 | { |
385 | } |
386 | |
387 | status_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. */ |
426 | const char CameraQemuClient::mQueryConnect[] = "connect"; |
427 | /* Disconect from the camera device. */ |
428 | const char CameraQemuClient::mQueryDisconnect[] = "disconnect"; |
429 | /* Start capturing video from the camera device. */ |
430 | const char CameraQemuClient::mQueryStart[] = "start"; |
431 | /* Stop capturing video from the camera device. */ |
432 | const char CameraQemuClient::mQueryStop[] = "stop"; |
433 | /* Get next video frame from the camera device. */ |
434 | const char CameraQemuClient::mQueryFrame[] = "frame"; |
435 | |
436 | CameraQemuClient::CameraQemuClient() |
437 | : QemuClient() |
438 | { |
439 | } |
440 | |
441 | CameraQemuClient::~CameraQemuClient() |
442 | { |
443 | |
444 | } |
445 | |
446 | status_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 | |
459 | status_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 | |
472 | status_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 | |
490 | status_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 | |
503 | status_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 |