diff options
Diffstat (limited to 'android/camera/camera-service.c')
-rw-r--r-- | android/camera/camera-service.c | 1468 |
1 files changed, 1468 insertions, 0 deletions
diff --git a/android/camera/camera-service.c b/android/camera/camera-service.c new file mode 100644 index 0000000..e551dca --- /dev/null +++ b/android/camera/camera-service.c @@ -0,0 +1,1468 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Contains emulated camera service implementation. + */ + +#include "qemu-common.h" +#include "android/globals.h" /* for android_hw */ +#include "android/hw-qemud.h" +#include "android/utils/misc.h" +#include "android/utils/system.h" +#include "android/utils/debug.h" +#include "android/camera/camera-capture.h" +#include "android/camera/camera-format-converters.h" +#include "android/camera/camera-service.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(camera,__VA_ARGS__) +#define D_ACTIVE VERBOSE_CHECK(camera) + +/* the T(...) macro is used to dump traffic */ +#define T_ACTIVE 0 + +#if T_ACTIVE +#define T(...) VERBOSE_PRINT(camera,__VA_ARGS__) +#else +#define T(...) ((void)0) +#endif + +/* Defines name of the camera service. */ +#define SERVICE_NAME "camera" + +/* Maximum number of supported emulated cameras. */ +#define MAX_CAMERA 8 + +/* Camera sevice descriptor. */ +typedef struct CameraServiceDesc CameraServiceDesc; +struct CameraServiceDesc { + /* Information about camera devices connected to the host. + * Note that once initialized, entries in this array are considered to be + * constant. */ + CameraInfo camera_info[MAX_CAMERA]; + /* Number of camera devices connected to the host. */ + int camera_count; +}; + +/* One and only one camera service. */ +static CameraServiceDesc _camera_service_desc; + +/******************************************************************************** + * Helper routines + *******************************************************************************/ + +/* A strict 'int' version of the 'strtol'. + * This routine is implemented on top of the standard 'strtol' for 32/64 bit + * portability. + */ +static int +strtoi(const char *nptr, char **endptr, int base) +{ + long val; + + errno = 0; + val = strtol(nptr, endptr, base); + if (errno) { + return (val == LONG_MAX) ? INT_MAX : INT_MIN; + } else { + if (val == (int)val) { + return (int)val; + } else { + errno = ERANGE; + return val > 0 ? INT_MAX : INT_MIN; + } + } +} + +/* Gets a parameter value out of the parameter string. + * All parameters that are passed to the camera service are formatted as such: + * "<name1>=<value1> <name2>=<value2> ... <nameN>=<valueN>" + * I.e.: + * - Every parameter must have a name, and a value. + * - Name and value must be separated with '='. + * - No spaces are allowed around '=' separating name and value. + * - Parameters must be separated with a single ' ' character. + * - No '=' character is allowed in name and in value. + * Param: + * params - String, containing the parameters. + * name - Parameter name. + * value - Upon success contains value for the given parameter. + * val_size - Size of the 'value' string buffer. + * Return: + * 0 on success, -1 if requested parameter is not found, or (a positive) number + * of bytes, required to make a copy of the parameter's value if 'value' string + * was too small to contain it. + */ +static int +_get_param_value(const char* params, const char* name, char* value, int val_size) +{ + const char* val_end; + int len = strlen(name); + const char* par_end = params + strlen(params); + const char* par_start = strstr(params, name); + + /* Search for 'name=' */ + while (par_start != NULL) { + /* Make sure that we're within the parameters buffer. */ + if ((par_end - par_start) < len) { + par_start = NULL; + break; + } + /* Make sure that par_start starts at the beginning of <name>, and only + * then check for '=' value separator. */ + if ((par_start == params || (*(par_start - 1) == ' ')) && + par_start[len] == '=') { + break; + } + /* False positive. Move on... */ + par_start = strstr(par_start + 1, name); + } + if (par_start == NULL) { + return -1; + } + + /* Advance past 'name=', and calculate value's string length. */ + par_start += len + 1; + val_end = strchr(par_start, ' '); + if (val_end == NULL) { + val_end = par_start + strlen(par_start); + } + len = val_end - par_start; + + /* Check if fits... */ + if ((len + 1) <= val_size) { + memcpy(value, par_start, len); + value[len] = '\0'; + return 0; + } else { + return len + 1; + } +} + +/* Gets a parameter value out of the parameter string. + * This routine is similar to _get_param_value, except it will always allocate + * a string buffer for the value. + * Param: + * params - String, containing the parameters. + * name - Parameter name. + * value - Upon success contains an allocated string containint the value for + * the given parameter. The caller is responsible for freeing the buffer + * returned in this parameter on success. + * Return: + * 0 on success, -1 if requested parameter is not found, or -2 on + * memory failure. + */ +static int +_get_param_value_alloc(const char* params, const char* name, char** value) +{ + char tmp; + int res; + + /* Calculate size of string buffer required for the value. */ + const int val_size = _get_param_value(params, name, &tmp, 0); + if (val_size < 0) { + *value = NULL; + return val_size; + } + + /* Allocate string buffer, and retrieve the value. */ + *value = (char*)malloc(val_size); + if (*value == NULL) { + E("%s: Unable to allocated %d bytes for string buffer.", + __FUNCTION__, val_size); + return -2; + } + res = _get_param_value(params, name, *value, val_size); + if (res) { + E("%s: Unable to retrieve value into allocated buffer.", __FUNCTION__); + free(*value); + *value = NULL; + } + + return res; +} + +/* Gets an integer parameter value out of the parameter string. + * Param: + * params - String, containing the parameters. See comments to _get_param_value + * routine on the parameters format. + * name - Parameter name. Parameter value must be a decimal number. + * value - Upon success contains integer value for the given parameter. + * Return: + * 0 on success, or -1 if requested parameter is not found, or -2 if parameter's + * format was bad (i.e. value was not a decimal number). + */ +static int +_get_param_value_int(const char* params, const char* name, int* value) +{ + char val_str[64]; // Should be enough for all numeric values. + if (!_get_param_value(params, name, val_str, sizeof(val_str))) { + errno = 0; + *value = strtoi(val_str, (char**)NULL, 10); + if (errno) { + E("%s: Value '%s' of the parameter '%s' in '%s' is not a decimal number.", + __FUNCTION__, val_str, name, params); + return -2; + } else { + return 0; + } + } else { + return -1; + } +} + +/* Extracts query name, and (optionally) query parameters from the query string. + * Param: + * query - Query string. Query string in the camera service are formatted as such: + * "<query name>[ <parameters>]", + * where parameters are optional, and if present, must be separated from the + * query name with a single ' '. See comments to _get_param_value routine + * for the format of the parameters string. + * query_name - Upon success contains query name extracted from the query + * string. + * query_name_size - Buffer size for 'query_name' string. + * query_param - Upon success contains a pointer to the beginning of the query + * parameters. If query has no parameters, NULL will be passed back with + * this parameter. This parameter is optional and can be NULL. + * Return: + * 0 on success, or number of bytes required for query name if 'query_name' + * string buffer was too small to contain it. + */ +static int +_parse_query(const char* query, + char* query_name, + int query_name_size, + const char** query_param) +{ + /* Extract query name. */ + const char* qend = strchr(query, ' '); + if (qend == NULL) { + qend = query + strlen(query); + } + if ((qend - query) >= query_name_size) { + return qend - query + 1; + } + memcpy(query_name, query, qend - query); + query_name[qend - query] = '\0'; + + /* Calculate query parameters pointer (if needed) */ + if (query_param != NULL) { + if (*qend == ' ') { + qend++; + } + *query_param = (*qend == '\0') ? NULL : qend; + } + + return 0; +} + +/* Appends one string to another, growing the destination string buffer if + * needed. + * Param: + * str_buffer - Contains pointer to the destination string buffer. Content of + * this parameter can be NULL. Note that content of this parameter will + * change if string buffer has been reallocated. + * str_buf_size - Contains current buffer size of the string, addressed by + * 'str_buffer' parameter. Note that content of this parameter will change + * if string buffer has been reallocated. + * str - String to append. + * Return: + * 0 on success, or -1 on failure (memory allocation). + */ +static int +_append_string(char** str_buf, size_t* str_buf_size, const char* str) +{ + const size_t offset = (*str_buf != NULL) ? strlen(*str_buf) : 0; + const size_t append_bytes = strlen(str) + 1; + + /* Make sure these two match. */ + if (*str_buf == NULL) { + *str_buf_size = 0; + } + + if ((offset + append_bytes) > *str_buf_size) { + /* Reallocate string, so it can fit what's being append to it. Note that + * we reallocate a bit bigger buffer than is needed in order to minimize + * number of memory allocation calls in case there are more "appends" + * coming. */ + const size_t required_mem = offset + append_bytes + 256; + char* new_buf = (char*)realloc(*str_buf, required_mem); + if (new_buf == NULL) { + E("%s: Unable to allocate %d bytes for a string", + __FUNCTION__, required_mem); + return -1; + } + *str_buf = new_buf; + *str_buf_size = required_mem; + } + memcpy(*str_buf + offset, str, append_bytes); + + return 0; +} + +/* Represents camera information as a string formatted as follows: + * 'name=<devname> channel=<num> pix=<format> facing=<direction> framedims=<widh1xheight1,...>\n' + * Param: + * ci - Camera information descriptor to convert into a string. + * str - Pointer to the string buffer where to save the converted camera + * information descriptor. On entry, content of this parameter can be NULL. + * Note that string buffer addressed with this parameter may be reallocated + * in this routine, so (if not NULL) it must contain a buffer allocated with + * malloc. The caller is responsible for freeing string buffer returned in + * this parameter. + * str_size - Contains byte size of the buffer addressed by 'str' parameter. + * Return: + * 0 on success, or != 0 on failure. + */ +static int +_camera_info_to_string(const CameraInfo* ci, char** str, size_t* str_size) { + int res; + int n; + char tmp[128]; + + /* Append device name. */ + snprintf(tmp, sizeof(tmp), "name=%s ", ci->device_name); + res = _append_string(str, str_size, tmp); + if (res) { + return res; + } + /* Append input channel. */ + snprintf(tmp, sizeof(tmp), "channel=%d ", ci->inp_channel); + res = _append_string(str, str_size, tmp); + if (res) { + return res; + } + /* Append pixel format. */ + snprintf(tmp, sizeof(tmp), "pix=%d ", ci->pixel_format); + res = _append_string(str, str_size, tmp); + if (res) { + return res; + } + /* Append direction. */ + snprintf(tmp, sizeof(tmp), "dir=%s ", ci->direction); + res = _append_string(str, str_size, tmp); + if (res) { + return res; + } + /* Append supported frame sizes. */ + snprintf(tmp, sizeof(tmp), "framedims=%dx%d", + ci->frame_sizes[0].width, ci->frame_sizes[0].height); + res = _append_string(str, str_size, tmp); + if (res) { + return res; + } + for (n = 1; n < ci->frame_sizes_num; n++) { + snprintf(tmp, sizeof(tmp), ",%dx%d", + ci->frame_sizes[n].width, ci->frame_sizes[n].height); + res = _append_string(str, str_size, tmp); + if (res) { + return res; + } + } + + /* Stringified camera properties should end with EOL. */ + return _append_string(str, str_size, "\n"); +} + +/* Gets camera information matching a display name. + * Param: + * disp_name - Display name to match. + * arr - Array of camera informations. + * num - Number of elements in the array. + * Return: + * Matching camera information, or NULL if matching camera information for the + * given display name has not been found in the array. + */ +static CameraInfo* +_camera_info_get_by_display_name(const char* disp_name, CameraInfo* arr, int num) +{ + int n; + for (n = 0; n < num; n++) { + if (arr[n].display_name != NULL && !strcmp(arr[n].display_name, disp_name)) { + return &arr[n]; + } + } + return NULL; +} + +/* Gets camera information matching a device name. + * Param: + * device_name - Device name to match. + * arr - Array of camera informations. + * num - Number of elements in the array. + * Return: + * Matching camera information, or NULL if matching camera information for the + * given device name has not been found in the array. + */ +static CameraInfo* +_camera_info_get_by_device_name(const char* device_name, CameraInfo* arr, int num) +{ + int n; + for (n = 0; n < num; n++) { + if (arr[n].device_name != NULL && !strcmp(arr[n].device_name, device_name)) { + return &arr[n]; + } + } + return NULL; +} + +/******************************************************************************** + * CameraServiceDesc API + *******************************************************************************/ + +/* Initializes camera service descriptor. + */ +static void +_camera_service_init(CameraServiceDesc* csd) +{ + CameraInfo ci[MAX_CAMERA]; + int connected_cnt; + int i; + + /* Enumerate camera devices connected to the host. */ + memset(ci, 0, sizeof(CameraInfo) * MAX_CAMERA); + memset(csd->camera_info, 0, sizeof(CameraInfo) * MAX_CAMERA); + csd->camera_count = 0; + connected_cnt = enumerate_camera_devices(ci, MAX_CAMERA); + if (connected_cnt <= 0) { + /* Nothing is connected - nothing to emulate. */ + return; + } + + /* For each webcam declared in hw.ini find an actual camera information + * descriptor, and save it into the service descriptor for the emulation. + * Stop the loop when all the connected cameras have been added to the + * service. */ + for (i = 0; i < android_hw->hw_webcam_count && + csd->camera_count < connected_cnt; i++) { + const char* disp_name; + const char* dir; + CameraInfo* found; + + switch (i) { + case 0: + disp_name = android_hw->hw_webcam_0_name; + dir = android_hw->hw_webcam_0_direction; + break; + case 1: + disp_name = android_hw->hw_webcam_1_name; + dir = android_hw->hw_webcam_1_direction; + break; + case 2: + disp_name = android_hw->hw_webcam_2_name; + dir = android_hw->hw_webcam_2_direction; + break; + case 3: + disp_name = android_hw->hw_webcam_3_name; + dir = android_hw->hw_webcam_3_direction; + break; + case 4: + disp_name = android_hw->hw_webcam_4_name; + dir = android_hw->hw_webcam_4_direction; + break; + case 5: + default: + disp_name = android_hw->hw_webcam_5_name; + dir = android_hw->hw_webcam_5_direction; + break; + } + found = _camera_info_get_by_display_name(disp_name, ci, connected_cnt); + if (found != NULL) { + /* Save to the camera info array that will be used by the service. + * Note that we just copy everything over, and NULL the source + * record. */ + memcpy(csd->camera_info + csd->camera_count, found, sizeof(CameraInfo)); + /* Update direction parameter. */ + if (csd->camera_info[csd->camera_count].direction != NULL) { + free(csd->camera_info[csd->camera_count].direction); + } + csd->camera_info[csd->camera_count].direction = ASTRDUP(dir); + D("Camera %d '%s' connected to '%s' facing %s using %.4s pixel format", + csd->camera_count, csd->camera_info[csd->camera_count].display_name, + csd->camera_info[csd->camera_count].device_name, + csd->camera_info[csd->camera_count].direction, + (const char*)(&csd->camera_info[csd->camera_count].pixel_format)); + csd->camera_count++; + memset(found, 0, sizeof(CameraInfo)); + } else { + W("Camera name '%s' is not found in the list of connected cameras.\n" + "Use '-webcam list' emulator option to obtain the list of connected camera names.\n", + disp_name); + } + } + + /* Make sure that camera 0 and camera 1 are facing in opposite directions. + * If they don't the camera application will crash on an attempt to switch + * cameras. */ + if (csd->camera_count > 0) { + const char* cam2_dir = NULL; + const char* cam2_name = NULL; + if (csd->camera_count >= 2) { + cam2_dir = csd->camera_info[1].direction; + cam2_name = csd->camera_info[1].display_name; + } else if (strcmp(android_hw->hw_fakeCamera, "off")) { + cam2_dir = android_hw->hw_fakeCamera; + cam2_name = "fake camera"; + } + if (cam2_dir != NULL && !strcmp(csd->camera_info[0].direction, cam2_dir)) { + W("Cameras '%s' and '%s' are both facing %s.\n" + "It is required by the camera application that first two emulated cameras\n" + "are facing in opposite directions. If they both are facing in the same direction,\n" + "the camera application will crash on an attempt to switch the camera.\n", + csd->camera_info[0].display_name, cam2_name, cam2_dir); + + } + } +} + +/* Gets camera information for the given camera device name. + * Param: + * cs - Initialized camera service descriptor. + * device_name - Camera's device name to look up the information for. + * Return: + * Camera information pointer on success, or NULL if no camera information has + * been found for the given device name. + */ +static CameraInfo* +_camera_service_get_camera_info_by_device_name(CameraServiceDesc* cs, + const char* device_name) +{ + return _camera_info_get_by_device_name(device_name, cs->camera_info, + cs->camera_count); +} + +/******************************************************************************** + * Helpers for handling camera client queries + *******************************************************************************/ + +/* Formats paload size according to the protocol, and sends it to the client. + * To simplify endianess handling we convert payload size to an eight characters + * string, representing payload size value in hexadecimal format. + * Param: + * qc - Qemu client to send the payload size to. + * payload_size - Payload size to report to the client. + */ +static void +_qemu_client_reply_payload(QemudClient* qc, size_t payload_size) +{ + char payload_size_str[9]; + snprintf(payload_size_str, sizeof(payload_size_str), "%08x", payload_size); + qemud_client_send(qc, (const uint8_t*)payload_size_str, 8); +} + +/* + * Prefixes for replies to camera client queries. + */ + +/* Success, no data to send in reply. */ +#define OK_REPLY "ok" +/* Failure, no data to send in reply. */ +#define KO_REPLY "ko" +/* Success, there are data to send in reply. */ +#define OK_REPLY_DATA OK_REPLY ":" +/* Failure, there are data to send in reply. */ +#define KO_REPLY_DATA KO_REPLY ":" + +/* Builds and sends a reply to a query. + * All replies to a query in camera service have a prefix indicating whether the + * query has succeeded ("ok"), or failed ("ko"). The prefix can be followed by + * extra data, containing response to the query. In case there are extra data, + * they are separated from the prefix with a ':' character. + * Param: + * qc - Qemu client to send the reply to. + * ok_ko - An "ok", or "ko" selector, where 0 is for "ko", and !0 is for "ok". + * extra - Optional extra query data. Can be NULL. + * extra_size - Extra data size. + */ +static void +_qemu_client_query_reply(QemudClient* qc, + int ok_ko, + const void* extra, + size_t extra_size) +{ + const char* ok_ko_str; + size_t payload_size; + + /* Make sure extra_size is 0 if extra is NULL. */ + if (extra == NULL && extra_size != 0) { + W("%s: 'extra' = NULL, while 'extra_size' = %d", + __FUNCTION__, (int)extra_size); + extra_size = 0; + } + + /* Calculate total payload size, and select appropriate 'ok'/'ko' prefix */ + if (extra_size) { + /* 'extra' size + 2 'ok'/'ko' bytes + 1 ':' separator byte. */ + payload_size = extra_size + 3; + ok_ko_str = ok_ko ? OK_REPLY_DATA : KO_REPLY_DATA; + } else { + /* No extra data: just zero-terminated 'ok'/'ko'. */ + payload_size = 3; + ok_ko_str = ok_ko ? OK_REPLY : KO_REPLY; + } + + /* Send payload size first. */ + _qemu_client_reply_payload(qc, payload_size); + /* Send 'ok[:]'/'ko[:]' next. Note that if there is no extra data, we still + * need to send a zero-terminator for 'ok'/'ko' string instead of the ':' + * separator. So, one way or another, the prefix is always 3 bytes. */ + qemud_client_send(qc, (const uint8_t*)ok_ko_str, 3); + /* Send extra data (if present). */ + if (extra != NULL) { + qemud_client_send(qc, (const uint8_t*)extra, extra_size); + } +} + +/* Replies query success ("OK") back to the client. + * Param: + * qc - Qemu client to send the reply to. + * ok_str - An optional string containing query results. Can be NULL. + */ +static void +_qemu_client_reply_ok(QemudClient* qc, const char* ok_str) +{ + _qemu_client_query_reply(qc, 1, ok_str, + (ok_str != NULL) ? (strlen(ok_str) + 1) : 0); +} + +/* Replies query failure ("KO") back to the client. + * Param: + * qc - Qemu client to send the reply to. + * ko_str - An optional string containing reason for failure. Can be NULL. + */ +static void +_qemu_client_reply_ko(QemudClient* qc, const char* ko_str) +{ + _qemu_client_query_reply(qc, 0, ko_str, + (ko_str != NULL) ? (strlen(ko_str) + 1) : 0); +} + +/******************************************************************************** + * Camera Factory API + *******************************************************************************/ + +/* Handles 'list' query received from the Factory client. + * Response to this query is a string that represents each connected camera in + * this format: 'name=devname framedims=widh1xheight1,widh2xheight2,widhNxheightN\n' + * Strings, representing each camera are separated with EOL symbol. + * Param: + * csd, client - Factory serivice, and client. + * Return: + * 0 on success, or != 0 on failure. + */ +static int +_factory_client_list_cameras(CameraServiceDesc* csd, QemudClient* client) +{ + int n; + size_t reply_size = 0; + char* reply = NULL; + + /* Lets see if there was anything found... */ + if (csd->camera_count == 0) { + /* No cameras connected to the host. Reply with "\n" */ + _qemu_client_reply_ok(client, "\n"); + return 0; + } + + /* "Stringify" each camera information into the reply string. */ + for (n = 0; n < csd->camera_count; n++) { + const int res = + _camera_info_to_string(csd->camera_info + n, &reply, &reply_size); + if (res) { + if (reply != NULL) { + free(reply); + } + _qemu_client_reply_ko(client, "Memory allocation error"); + return res; + } + } + + D("%s Replied: %s", __FUNCTION__, reply); + _qemu_client_reply_ok(client, reply); + free(reply); + + return 0; +} + +/* Handles a message received from the emulated camera factory client. + * Queries received here are represented as strings: + * 'list' - Queries list of cameras connected to the host. + * Param: + * opaque - Camera service descriptor. + * msg, msglen - Message received from the camera factory client. + * client - Camera factory client pipe. + */ +static void +_factory_client_recv(void* opaque, + uint8_t* msg, + int msglen, + QemudClient* client) +{ + /* + * Emulated camera factory client queries. + */ + + /* List cameras connected to the host. */ + static const char _query_list[] = "list"; + + CameraServiceDesc* csd = (CameraServiceDesc*)opaque; + char query_name[64]; + const char* query_param = NULL; + + /* Parse the query, extracting query name and parameters. */ + if (_parse_query((const char*)msg, query_name, sizeof(query_name), + &query_param)) { + E("%s: Invalid format in query '%s'", __FUNCTION__, (const char*)msg); + _qemu_client_reply_ko(client, "Invalid query format"); + return; + } + + D("%s Camera factory query '%s'", __FUNCTION__, query_name); + + /* Dispatch the query to an appropriate handler. */ + if (!strcmp(query_name, _query_list)) { + /* This is a "list" query. */ + _factory_client_list_cameras(csd, client); + } else { + E("%s: Unknown camera factory query name in '%s'", + __FUNCTION__, (const char*)msg); + _qemu_client_reply_ko(client, "Unknown query name"); + } +} + +/* Emulated camera factory client has been disconnected from the service. */ +static void +_factory_client_close(void* opaque) +{ + /* There is nothing to clean up here: factory service is just an alias for + * the "root" camera service, that doesn't require anything more, than camera + * dervice descriptor already provides. */ +} + +/******************************************************************************** + * Camera client API + *******************************************************************************/ + +/* Describes an emulated camera client. + */ +typedef struct CameraClient CameraClient; +struct CameraClient +{ + /* Client name. + * On Linux this is the name of the camera device. + * On Windows this is the name of capturing window. + */ + char* device_name; + /* Input channel to use to connect to the camera. */ + int inp_channel; + /* Camera information. */ + const CameraInfo* camera_info; + /* Emulated camera device descriptor. */ + CameraDevice* camera; + /* Buffer allocated for video frames. + * Note that memory allocated for this buffer + * also contains preview framebuffer. */ + uint8_t* video_frame; + /* Preview frame buffer. + * This address points inside the 'video_frame' buffer. */ + uint16_t* preview_frame; + /* Byte size of the videoframe buffer. */ + size_t video_frame_size; + /* Byte size of the preview frame buffer. */ + size_t preview_frame_size; + /* Pixel format required by the guest. */ + uint32_t pixel_format; + /* Frame width. */ + int width; + /* Frame height. */ + int height; + /* Number of pixels in a frame buffer. */ + int pixel_num; + /* Status of video and preview frame cache. */ + int frames_cached; +}; + +/* Frees emulated camera client descriptor. */ +static void +_camera_client_free(CameraClient* cc) +{ + /* The only exception to the "read only" rule: we have to mark the camera + * as being not used when we destroy a service for it. */ + if (cc->camera_info != NULL) { + ((CameraInfo*)cc->camera_info)->in_use = 0; + } + if (cc->camera != NULL) { + camera_device_close(cc->camera); + } + if (cc->video_frame != NULL) { + free(cc->video_frame); + } + if (cc->device_name != NULL) { + free(cc->device_name); + } + + AFREE(cc); +} + +/* Creates descriptor for a connecting emulated camera client. + * Param: + * csd - Camera service descriptor. + * param - Client parameters. Must be formatted as described in comments to + * _get_param_value routine, and must contain at least 'name' parameter, + * identifiying the camera device to create the service for. Also parameters + * may contain a decimal 'inp_channel' parameter, selecting the input + * channel to use when communicating with the camera device. + * Return: + * Emulated camera client descriptor on success, or NULL on failure. + */ +static CameraClient* +_camera_client_create(CameraServiceDesc* csd, const char* param) +{ + CameraClient* cc; + CameraInfo* ci; + int res; + ANEW0(cc); + + /* + * Parse parameter string, containing camera client properties. + */ + + /* Pull required device name. */ + if (_get_param_value_alloc(param, "name", &cc->device_name)) { + E("%s: Allocation failure, or required 'name' parameter is missing, or misformed in '%s'", + __FUNCTION__, param); + return NULL; + } + + /* Pull optional input channel. */ + res = _get_param_value_int(param, "inp_channel", &cc->inp_channel); + if (res != 0) { + if (res == -1) { + /* 'inp_channel' parameter has been ommited. Use default input + * channel, which is zero. */ + cc->inp_channel = 0; + } else { + E("%s: 'inp_channel' parameter is misformed in '%s'", + __FUNCTION__, param); + return NULL; + } + } + + /* Get camera info for the emulated camera represented with this service. + * Array of camera information records has been created when the camera + * service was enumerating camera devices during the service initialization. + * By the camera service protocol, camera service clients must first obtain + * list of enumerated cameras via the 'list' query to the camera service, and + * then use device name reported in the list to connect to an emulated camera + * service. So, if camera information for the given device name is not found + * in the array, we fail this connection due to protocol violation. */ + ci = _camera_service_get_camera_info_by_device_name(csd, cc->device_name); + if (ci == NULL) { + E("%s: Cannot find camera info for device '%s'", + __FUNCTION__, cc->device_name); + _camera_client_free(cc); + return NULL; + } + + /* We can't allow multiple camera services for a single camera device, Lets + * make sure that there is no client created for this camera. */ + if (ci->in_use) { + E("%s: Camera device '%s' is in use", __FUNCTION__, cc->device_name); + _camera_client_free(cc); + return NULL; + } + + /* We're done. Set camera in use, and succeed the connection. */ + ci->in_use = 1; + cc->camera_info = ci; + + D("%s: Camera service is created for device '%s' using input channel %d", + __FUNCTION__, cc->device_name, cc->inp_channel); + + return cc; +} + +/******************************************************************************** + * Camera client queries + *******************************************************************************/ + +/* Client has queried conection to the camera. + * Param: + * cc - Queried camera client descriptor. + * qc - Qemu client for the emulated camera. + * param - Query parameters. There are no parameters expected for this query. + */ +static void +_camera_client_query_connect(CameraClient* cc, QemudClient* qc, const char* param) +{ + if (cc->camera != NULL) { + /* Already connected. */ + W("%s: Camera '%s' is already connected", __FUNCTION__, cc->device_name); + _qemu_client_reply_ok(qc, "Camera is already connected"); + return; + } + + /* Open camera device. */ + cc->camera = camera_device_open(cc->device_name, cc->inp_channel); + if (cc->camera == NULL) { + E("%s: Unable to open camera device '%s'", __FUNCTION__, cc->device_name); + _qemu_client_reply_ko(qc, "Unable to open camera device."); + return; + } + + D("%s: Camera device '%s' is now connected", __FUNCTION__, cc->device_name); + + _qemu_client_reply_ok(qc, NULL); +} + +/* Client has queried disconection from the camera. + * Param: + * cc - Queried camera client descriptor. + * qc - Qemu client for the emulated camera. + * param - Query parameters. There are no parameters expected for this query. + */ +static void +_camera_client_query_disconnect(CameraClient* cc, + QemudClient* qc, + const char* param) +{ + if (cc->camera == NULL) { + /* Already disconnected. */ + W("%s: Camera '%s' is already disconnected", __FUNCTION__, cc->device_name); + _qemu_client_reply_ok(qc, "Camera is not connected"); + return; + } + + /* Before we can go ahead and disconnect, we must make sure that camera is + * not capturing frames. */ + if (cc->video_frame != NULL) { + E("%s: Cannot disconnect camera '%s' while it is not stopped", + __FUNCTION__, cc->device_name); + _qemu_client_reply_ko(qc, "Camera is not stopped"); + return; + } + + /* Close camera device. */ + camera_device_close(cc->camera); + cc->camera = NULL; + + D("Camera device '%s' is now disconnected", cc->device_name); + + _qemu_client_reply_ok(qc, NULL); +} + +/* Client has queried the client to start capturing video. + * Param: + * cc - Queried camera client descriptor. + * qc - Qemu client for the emulated camera. + * param - Query parameters. Parameters for this query must contain a 'dim', and + * a 'pix' parameters, where 'dim' must be "dim=<width>x<height>", and 'pix' + * must be "pix=<format>", where 'width' and 'height' must be numerical + * values for the capturing video frame width, and height, and 'format' must + * be a numerical value for the pixel format of the video frames expected by + * the client. 'format' must be one of the V4L2_PIX_FMT_XXX values. + */ +static void +_camera_client_query_start(CameraClient* cc, QemudClient* qc, const char* param) +{ + char* w; + char dim[64]; + int width, height, pix_format; + + /* Sanity check. */ + if (cc->camera == NULL) { + /* Not connected. */ + E("%s: Camera '%s' is not connected", __FUNCTION__, cc->device_name); + _qemu_client_reply_ko(qc, "Camera is not connected"); + return; + } + + /* + * Parse parameters. + */ + + if (param == NULL) { + E("%s: Missing parameters for the query", __FUNCTION__); + _qemu_client_reply_ko(qc, "Missing parameters for the query"); + return; + } + + /* Pull required 'dim' parameter. */ + if (_get_param_value(param, "dim", dim, sizeof(dim))) { + E("%s: Invalid or missing 'dim' parameter in '%s'", __FUNCTION__, param); + _qemu_client_reply_ko(qc, "Invalid or missing 'dim' parameter"); + return; + } + + /* Pull required 'pix' parameter. */ + if (_get_param_value_int(param, "pix", &pix_format)) { + E("%s: Invalid or missing 'pix' parameter in '%s'", __FUNCTION__, param); + _qemu_client_reply_ko(qc, "Invalid or missing 'pix' parameter"); + return; + } + + /* Parse 'dim' parameter, and get requested frame width and height. */ + w = strchr(dim, 'x'); + if (w == NULL || w[1] == '\0') { + E("%s: Invalid 'dim' parameter in '%s'", __FUNCTION__, param); + _qemu_client_reply_ko(qc, "Invalid 'dim' parameter"); + return; + } + *w = '\0'; w++; + errno = 0; + width = strtoi(dim, NULL, 10); + height = strtoi(w, NULL, 10); + if (errno) { + E("%s: Invalid 'dim' parameter in '%s'", __FUNCTION__, param); + _qemu_client_reply_ko(qc, "Invalid 'dim' parameter"); + return; + } + + /* After collecting capture parameters lets see if camera has already + * started, and if so, lets see if parameters match. */ + if (cc->video_frame != NULL) { + /* Already started. Match capture parameters. */ + if (cc->pixel_format != pix_format ||cc->width != width || + cc->height != height) { + /* Parameters match. Succeed the query. */ + W("%s: Camera '%s' is already started", __FUNCTION__, cc->device_name); + _qemu_client_reply_ok(qc, "Camera is already started"); + } else { + /* Parameters don't match. Fail the query. */ + E("%s: Camera '%s' is already started, and parameters don't match:\n" + "Current %.4s[%dx%d] != requested %.4s[%dx%d]", + __FUNCTION__, cc->device_name, (const char*)&cc->pixel_format, + cc->width, cc->height, (const char*)&pix_format, width, height); + _qemu_client_reply_ko(qc, + "Camera is already started with different capturing parameters"); + } + return; + } + + /* + * Start the camera. + */ + + /* Save capturing parameters. */ + cc->pixel_format = pix_format; + cc->width = width; + cc->height = height; + cc->pixel_num = cc->width * cc->height; + cc->frames_cached = 0; + + /* Make sure that pixel format is known, and calculate video framebuffer size + * along the lines. */ + switch (cc->pixel_format) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + cc->video_frame_size = (cc->pixel_num * 12) / 8; + break; + + default: + E("%s: Unknown pixel format %.4s", + __FUNCTION__, (char*)&cc->pixel_format); + _qemu_client_reply_ko(qc, "Pixel format is unknown"); + return; + } + + /* Make sure that we have a converters between the original camera pixel + * format and the one that the client expects. Also a converter must exist + * for the preview window pixel format (RGB32) */ + if (!has_converter(cc->camera_info->pixel_format, cc->pixel_format) || + !has_converter(cc->camera_info->pixel_format, V4L2_PIX_FMT_RGB32)) { + E("%s: No conversion exist between %.4s and %.4s (or RGB32) pixel formats", + __FUNCTION__, (char*)&cc->camera_info->pixel_format, (char*)&cc->pixel_format); + _qemu_client_reply_ko(qc, "No conversion exist for the requested pixel format"); + return; + } + + /* TODO: At the moment camera framework in the emulator requires RGB32 pixel + * format for preview window. So, we need to keep two framebuffers here: one + * for the video, and another for the preview window. Watch out when this + * changes (if changes). */ + cc->preview_frame_size = cc->pixel_num * 4; + + /* Allocate buffer large enough to contain both, video and preview + * framebuffers. */ + cc->video_frame = + (uint8_t*)malloc(cc->video_frame_size + cc->preview_frame_size); + if (cc->video_frame == NULL) { + E("%s: Not enough memory for framebuffers %d + %d", + __FUNCTION__, cc->video_frame_size, cc->preview_frame_size); + _qemu_client_reply_ko(qc, "Out of memory"); + return; + } + + /* Set framebuffer pointers. */ + cc->preview_frame = (uint16_t*)(cc->video_frame + cc->video_frame_size); + + /* Start the camera. */ + if (camera_device_start_capturing(cc->camera, cc->camera_info->pixel_format, + cc->width, cc->height)) { + E("%s: Cannot start camera '%s' for %.4s[%dx%d]: %s", + __FUNCTION__, cc->device_name, (const char*)&cc->pixel_format, + cc->width, cc->height, strerror(errno)); + free(cc->video_frame); + cc->video_frame = NULL; + _qemu_client_reply_ko(qc, "Cannot start the camera"); + return; + } + + D("%s: Camera '%s' is now started for %.4s[%dx%d]", + __FUNCTION__, cc->device_name, (char*)&cc->pixel_format, cc->width, + cc->height); + + _qemu_client_reply_ok(qc, NULL); +} + +/* Client has queried the client to stop capturing video. + * Param: + * cc - Queried camera client descriptor. + * qc - Qemu client for the emulated camera. + * param - Query parameters. There are no parameters expected for this query. + */ +static void +_camera_client_query_stop(CameraClient* cc, QemudClient* qc, const char* param) +{ + if (cc->video_frame == NULL) { + /* Not started. */ + W("%s: Camera '%s' is not started", __FUNCTION__, cc->device_name); + _qemu_client_reply_ok(qc, "Camera is not started"); + return; + } + + /* Stop the camera. */ + if (camera_device_stop_capturing(cc->camera)) { + E("%s: Cannot stop camera device '%s': %s", + __FUNCTION__, cc->device_name, strerror(errno)); + _qemu_client_reply_ko(qc, "Cannot stop camera device"); + return; + } + + free(cc->video_frame); + cc->video_frame = NULL; + + D("%s: Camera device '%s' is now stopped.", __FUNCTION__, cc->device_name); + _qemu_client_reply_ok(qc, NULL); +} + +/* Client has queried next frame. + * Param: + * cc - Queried camera client descriptor. + * qc - Qemu client for the emulated camera. + * param - Query parameters. Parameters for this query must contain a 'video', + * and a 'preview' parameters, both must be decimal values, defining size of + * requested video, and preview frames respectively. Zero value for any of + * the parameters means that this particular frame is not requested. + */ +static void +_camera_client_query_frame(CameraClient* cc, QemudClient* qc, const char* param) +{ + int video_size = 0; + int preview_size = 0; + int repeat; + ClientFrameBuffer fbs[2]; + int fbs_num = 0; + size_t payload_size; + uint64_t tick; + + /* Sanity check. */ + if (cc->video_frame == NULL) { + /* Not started. */ + E("%s: Camera '%s' is not started", __FUNCTION__, cc->device_name); + _qemu_client_reply_ko(qc, "Camera is not started"); + return; + } + + /* Pull required parameters. */ + if (_get_param_value_int(param, "video", &video_size) || + _get_param_value_int(param, "preview", &preview_size)) { + E("%s: Invalid or missing 'video', or 'preview' parameter in '%s'", + __FUNCTION__, param); + _qemu_client_reply_ko(qc, + "Invalid or missing 'video', or 'preview' parameter"); + return; + } + + /* Verify that framebuffer sizes match the ones that the started camera + * operates with. */ + if ((video_size != 0 && cc->video_frame_size != video_size) || + (preview_size != 0 && cc->preview_frame_size != preview_size)) { + E("%s: Frame sizes don't match for camera '%s':\n" + "Expected %d for video, and %d for preview. Requested %d, and %d", + __FUNCTION__, cc->device_name, cc->video_frame_size, + cc->preview_frame_size, video_size, preview_size); + _qemu_client_reply_ko(qc, "Frame size mismatch"); + return; + } + + /* + * Initialize framebuffer array for frame read. + */ + + if (video_size) { + fbs[fbs_num].pixel_format = cc->pixel_format; + fbs[fbs_num].framebuffer = cc->video_frame; + fbs_num++; + } + if (preview_size) { + /* TODO: Watch out for preview format changes! */ + fbs[fbs_num].pixel_format = V4L2_PIX_FMT_RGB32; + fbs[fbs_num].framebuffer = cc->preview_frame; + fbs_num++; + } + + /* Capture new frame. */ + tick = _get_timestamp(); + repeat = camera_device_read_frame(cc->camera, fbs, fbs_num); + + /* Note that there is no (known) way how to wait on next frame being + * available, so we could dequeue frame buffer from the device only when we + * know it's available. Instead we're shooting in the dark, and quite often + * device will response with EAGAIN, indicating that it doesn't have frame + * ready. In turn, it means that the last frame we have obtained from the + * device is still good, and we can reply with the cached frames. The only + * case when we need to keep trying to obtain a new frame is when frame cache + * is empty. To prevent ourselves from an indefinite loop in case device got + * stuck on something (observed with some Microsoft devices) we will limit + * the loop by 2 second time period (which is more than enough to obtain + * something from the device) */ + while (repeat == 1 && !cc->frames_cached && + (_get_timestamp() - tick) < 2000000LL) { + /* Sleep for 10 millisec before repeating the attempt. */ + _camera_sleep(10); + repeat = camera_device_read_frame(cc->camera, fbs, fbs_num); + } + if (repeat == 1 && !cc->frames_cached) { + /* Waited too long for the first frame. */ + E("%s: Unable to obtain first video frame from the camera '%s' in %d milliseconds: %s.", + __FUNCTION__, cc->device_name, + (uint32_t)(_get_timestamp() - tick) / 1000, strerror(errno)); + _qemu_client_reply_ko(qc, "Unable to obtain video frame from the camera"); + return; + } else if (repeat < 0) { + /* An I/O error. */ + E("%s: Unable to obtain video frame from the camera '%s': %s.", + __FUNCTION__, cc->device_name, strerror(errno)); + _qemu_client_reply_ko(qc, strerror(errno)); + return; + } + + /* We have cached something... */ + cc->frames_cached = 1; + + /* + * Build the reply. + */ + + /* Payload includes "ok:" + requested video and preview frames. */ + payload_size = 3 + video_size + preview_size; + + /* Send payload size first. */ + _qemu_client_reply_payload(qc, payload_size); + + /* After that send the 'ok:'. Note that if there is no frames sent, we should + * use prefix "ok" instead of "ok:" */ + if (video_size || preview_size) { + qemud_client_send(qc, (const uint8_t*)"ok:", 3); + } else { + /* Still 3 bytes: zero terminator is required in this case. */ + qemud_client_send(qc, (const uint8_t*)"ok", 3); + } + + /* After that send video frame (if requested). */ + if (video_size) { + qemud_client_send(qc, cc->video_frame, video_size); + } + + /* After that send preview frame (if requested). */ + if (preview_size) { + qemud_client_send(qc, (const uint8_t*)cc->preview_frame, preview_size); + } +} + +/* Handles a message received from the emulated camera client. + * Queries received here are represented as strings: + * - 'connect' - Connects to the camera device (opens it). + * - 'disconnect' - Disconnexts from the camera device (closes it). + * - 'start' - Starts capturing video from the connected camera device. + * - 'stop' - Stop capturing video from the connected camera device. + * - 'frame' - Queries video and preview frames captured from the camera. + * Param: + * opaque - Camera service descriptor. + * msg, msglen - Message received from the camera factory client. + * client - Camera factory client pipe. + */ +static void +_camera_client_recv(void* opaque, + uint8_t* msg, + int msglen, + QemudClient* client) +{ + /* + * Emulated camera client queries. + */ + + /* Connect to the camera. */ + static const char _query_connect[] = "connect"; + /* Disconnect from the camera. */ + static const char _query_disconnect[] = "disconnect"; + /* Start video capturing. */ + static const char _query_start[] = "start"; + /* Stop video capturing. */ + static const char _query_stop[] = "stop"; + /* Query frame(s). */ + static const char _query_frame[] = "frame"; + + char query_name[64]; + const char* query_param; + CameraClient* cc = (CameraClient*)opaque; + + /* + * Emulated camera queries are formatted as such: + * "<query name> [<parameters>]" + */ + + T("%s: Camera client query: '%s'", __FUNCTION__, (char*)msg); + if (_parse_query((const char*)msg, query_name, sizeof(query_name), + &query_param)) { + E("%s: Invalid query '%s'", __FUNCTION__, (char*)msg); + _qemu_client_reply_ko(client, "Invalid query"); + return; + } + + /* Dispatch the query to an appropriate handler. */ + if (!strcmp(query_name, _query_frame)) { + /* A frame is queried. */ + _camera_client_query_frame(cc, client, query_param); + } else if (!strcmp(query_name, _query_connect)) { + /* Camera connection is queried. */ + _camera_client_query_connect(cc, client, query_param); + } else if (!strcmp(query_name, _query_disconnect)) { + /* Camera disnection is queried. */ + _camera_client_query_disconnect(cc, client, query_param); + } else if (!strcmp(query_name, _query_start)) { + /* Start capturing is queried. */ + _camera_client_query_start(cc, client, query_param); + } else if (!strcmp(query_name, _query_stop)) { + /* Stop capturing is queried. */ + _camera_client_query_stop(cc, client, query_param); + } else { + E("%s: Unknown query '%s'", __FUNCTION__, (char*)msg); + _qemu_client_reply_ko(client, "Unknown query"); + } +} + +/* Emulated camera client has been disconnected from the service. */ +static void +_camera_client_close(void* opaque) +{ + CameraClient* cc = (CameraClient*)opaque; + + D("%s: Camera client for device '%s' on input channel %d is now closed", + __FUNCTION__, cc->device_name, cc->inp_channel); + + _camera_client_free(cc); +} + +/******************************************************************************** + * Camera service API + *******************************************************************************/ + +/* Connects a client to the camera service. + * There are two classes of the client that can connect to the service: + * - Camera factory that is insterested only in listing camera devices attached + * to the host. + * - Camera device emulators that attach to the actual camera devices. + * The distinction between these two classes is made by looking at extra + * parameters passed in client_param variable. If it's NULL, or empty, the client + * connects to a camera factory. Otherwise, parameters describe the camera device + * the client wants to connect to. + */ +static QemudClient* +_camera_service_connect(void* opaque, + QemudService* serv, + int channel, + const char* client_param) +{ + QemudClient* client = NULL; + CameraServiceDesc* csd = (CameraServiceDesc*)opaque; + + D("%s: Connecting camera client '%s'", + __FUNCTION__, client_param ? client_param : "Factory"); + if (client_param == NULL || *client_param == '\0') { + /* This is an emulated camera factory client. */ + client = qemud_client_new(serv, channel, client_param, csd, + _factory_client_recv, _factory_client_close, + NULL, NULL); + } else { + /* This is an emulated camera client. */ + CameraClient* cc = _camera_client_create(csd, client_param); + if (cc != NULL) { + client = qemud_client_new(serv, channel, client_param, cc, + _camera_client_recv, _camera_client_close, + NULL, NULL); + } + } + + return client; +} + +void +android_camera_service_init(void) +{ + static int _inited = 0; + + if (!_inited) { + _camera_service_init(&_camera_service_desc); + QemudService* serv = qemud_service_register( SERVICE_NAME, 0, + &_camera_service_desc, + _camera_service_connect, + NULL, NULL); + if (serv == NULL) { + derror("%s: Could not register '%s' service", + __FUNCTION__, SERVICE_NAME); + return; + } + D("%s: Registered '%s' qemud service", __FUNCTION__, SERVICE_NAME); + } +} + +void +android_list_web_cameras(void) +{ + CameraInfo ci[MAX_CAMERA]; + int connected_cnt; + int i; + + /* Enumerate camera devices connected to the host. */ + connected_cnt = enumerate_camera_devices(ci, MAX_CAMERA); + if (connected_cnt <= 0) { + return; + } + + printf("List of web cameras connected to the computer:\n"); + for (i = 0; i < connected_cnt; i++) { + printf(" Camera '%s' is connected to device '%s' on channel %d using pixel format '%.4s'\n", + ci[i].display_name, ci[i].device_name, ci[i].inp_channel, + (const char*)&ci[i].pixel_format); + } + printf("\n"); +} |