Home | History | Annotate | Download | only in camera
      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