QemuClient.cpp revision 5a622cba8c5287d5e6577f940a22343a7cae977f
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 %d 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 %d 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 %d: %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 %d 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=%d preview=%d 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 %d bytes is to small to contain %d 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 %d bytes is to small to contain %d 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