aboutsummaryrefslogtreecommitdiffstats
path: root/android/camera/camera-service.c
diff options
context:
space:
mode:
Diffstat (limited to 'android/camera/camera-service.c')
-rw-r--r--android/camera/camera-service.c1215
1 files changed, 1098 insertions, 117 deletions
diff --git a/android/camera/camera-service.c b/android/camera/camera-service.c
index 077809d..c3b309f 100644
--- a/android/camera/camera-service.c
+++ b/android/camera/camera-service.c
@@ -32,13 +32,30 @@
#define E(...) VERBOSE_PRINT(camera,__VA_ARGS__)
#define D_ACTIVE VERBOSE_CHECK(camera)
-#define SERVICE_NAME "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;
+ int camera_count;
};
/* One and only one camera service. */
@@ -51,16 +68,512 @@ static CameraServiceDesc _camera_service_desc;
/* Initializes camera service descriptor.
*/
static void
-_csDesc_init(CameraServiceDesc* csd)
+_camera_service_init(CameraServiceDesc* csd)
+{
+ /* Enumerate camera devices connected to the host. */
+ csd->camera_count = enumerate_camera_devices(csd->camera_info, MAX_CAMERA);
+ if (csd->camera_count >= 0) {
+ D("%s: Enumerated %d cameras connected to the host",
+ __FUNCTION__, csd->camera_count);
+ } else {
+ E("%s: Unable to enumerate camera devices", __FUNCTION__);
+ csd->camera_count = 0;
+ return;
+ }
+}
+
+/* Gets camera information for the given camera device name.
+ * Param:
+ * cs - Initialized camera service descriptor.
+ * name - Camera 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. Note that camera information returned
+ * from this routine is constant.
+ */
+static CameraInfo*
+_camera_service_get_camera_info(CameraServiceDesc* cs, const char* name)
{
- csd->camera_count = 0;
+ int n;
+ for (n = 0; n < cs->camera_count; n++) {
+ if (!strcmp(cs->camera_info[n].device_name, name)) {
+ return &cs->camera_info[n];
+ }
+ }
+ return NULL;
+}
+
+/********************************************************************************
+ * 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> framedims=<widh1xheight1,widh2xheight2,widhNxheightN>\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 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");
+}
+
+/********************************************************************************
+ * 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__, extra, 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,
@@ -68,14 +581,45 @@ _factory_client_recv(void* opaque,
int msglen,
QemudClient* client)
{
- // TODO: implement.
+ /*
+ * 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.
- */
+/* 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. */
}
/********************************************************************************
@@ -91,151 +635,537 @@ struct CameraClient
* On Linux this is the name of the camera device.
* On Windows this is the name of capturing window.
*/
- char* name;
-
+ char* device_name;
/* Input channel to use to connect to the camera. */
- int inp_channel;
-
- /* Extra parameters passed to the client. */
- char* remaining_param;
+ 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;
+ /* Point to Cb pane inside the video frame buffer. */
+ uint8_t* video_frame_Cb;
+ /* Point to Cr pane inside the video frame buffer. */
+ uint8_t* video_frame_Cr;
+ /* 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.
- */
+/* Frees emulated camera client descriptor. */
static void
_camera_client_free(CameraClient* cc)
{
- if (cc->remaining_param != NULL) {
- free(cc->remaining_param);
+ /* 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->name != NULL) {
- free(cc->name);
+ if (cc->device_name != NULL) {
+ free(cc->device_name);
}
AFREE(cc);
}
-/* Parses emulated camera client parameters.
+/* Creates descriptor for a connecting emulated camera client.
* Param:
- * param - Parameters to parse. This string contains multiple parameters,
- * separated by a ':' character. The format of the parameters string is as
- * follows:
- * <device name>[:<input channel #>][:<extra param>],
- * where 'device name' is a required parameter defining name of the camera
- * device, 'input channel' is an optional parameter (positive integer),
- * defining input channel to use on the camera device. Format of the
- * extra parameters is not defined at this point.
- * device_name - Upon success contains camera device name. The caller is
- * responsible for freeing string buffer returned here.
- * inp_channel - Upon success contains the input channel to use when connecting
- * to the device. If this parameter is missing, a 0 will be returned here.
- * remainder - Contains copy of the string containing remander of the parameters
- * following device name and input channel. If there are no remainding
- * parameters, a NULL will be returned here. The caller is responsible for
- * freeing string buffer returned here.
+ * 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:
- * 0 on success, or !0 on failure.
+ * Emulated camera client descriptor on success, or NULL on failure.
*/
-static int
-_parse_camera_client_param(const char* param,
- char** device_name,
- int* inp_channel,
- char** remainder)
+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(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)
{
- const char* wrk = param;
- const char* sep;
+ 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;
+ }
- *device_name = *remainder = NULL;
- *inp_channel = 0;
+ /* 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 (param == NULL || *param == '\0') {
- E("%s: Parameters must contain device name", __FUNCTION__);
- return -1;
+ 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;
}
- /* Must contain device name. */
- sep = strchr(wrk, ':');
- if (sep == NULL) {
- /* Contains only device name. */
- *device_name = ASTRDUP(param);
- return 0;
+ /*
+ * Parse parameters.
+ */
+
+ if (param == NULL) {
+ E("%s: Missing parameters for the query", __FUNCTION__);
+ _qemu_client_reply_ko(qc, "Missing parameters for the query");
+ return;
}
- /* Save device name. */
- *device_name = (char*)malloc((sep - wrk) + 1);
- if (*device_name == NULL) {
- derror("%s: Not enough memory", __FUNCTION__);
- return -1;
+ /* 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;
}
- memcpy(*device_name, wrk, sep - wrk);
- (*device_name)[sep - wrk] = '\0';
- /* Move on to the the input channel. */
- wrk = sep + 1;
- if (*wrk == '\0') {
- return 0;
+ /* 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;
}
- sep = strchr(wrk, ':');
- if (sep == NULL) {
- sep = wrk + strlen(wrk);
+
+ /* 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;
}
- errno = 0; // strtol doesn't set it on success.
- *inp_channel = strtol(wrk, (char**)&sep, 10);
- if (errno != 0) {
- E("%s: Parameters %s contain invalid input channel",
- __FUNCTION__, param);
- free(*device_name);
- *device_name = NULL;
- return -1;
+ *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;
}
- if (*sep == '\0') {
- return 0;
+
+ /* 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;
}
- /* Move on to the the remaining string. */
- wrk = sep + 1;
- if (*wrk == '\0') {
- return 0;
+ /*
+ * 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_YVU420:
+ 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;
}
- *remainder = ASTRDUP(wrk);
- return 0;
+ /* 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);
+ /* TODO: Get rid if this! It assumes that client's framebuffer is YV12. Let
+ * the camera do the conversion. All we need is to ensure that framebuffers
+ * allocated here are large enough! */
+ cc->video_frame_Cb = cc->video_frame + cc->pixel_num;
+ cc->video_frame_Cr = cc->video_frame_Cb + cc->pixel_num / 4;
+
+ /* 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);
}
-/* Creates descriptor for a connecting emulated camera client.
+/* Client has queried the client to stop capturing video.
* Param:
- * csd - Camera service descriptor.
- * param - Client parameters. Must be formatted as follows:
- * - Multiple parameters are separated by ':'
- * - Must begin with camera device name
- * - Can follow with an optional input channel number, wich must be an
- * integer value
- * Return:
- * Emulated camera client descriptor on success, or NULL on failure.
+ * cc - Queried camera client descriptor.
+ * qc - Qemu client for the emulated camera.
+ * param - Query parameters. There are no parameters expected for this query.
*/
-static CameraClient*
-_camera_client_create(CameraServiceDesc* csd, const char* param)
+static void
+_camera_client_query_stop(CameraClient* cc, QemudClient* qc, const char* param)
{
- CameraClient* cc;
- int res;
+ 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;
+ }
- ANEW0(cc);
+ /* 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;
+ }
- /* Parse parameters, and save them to the client. */
- res = _parse_camera_client_param(param, &cc->name, &cc->inp_channel,
- &cc->remaining_param);
- if (res) {
- _camera_client_free(cc);
- return NULL;
+ 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;
+
+ /* 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;
}
- D("Camera client created: name=%s, inp_channel=%d",
- cc->name, cc->inp_channel);
- return cc;
+ /* 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. */
+ 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 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. */
+ while (repeat == 1 && !cc->frames_cached) {
+ repeat = camera_device_read_frame(cc->camera, fbs, fbs_num);
+ }
+ if (repeat < 0) {
+ E("%s: Unable to obtain video frame from the camera '%s': %s",
+ __FUNCTION__, cc->device_name, strerror(errno));
+ _qemu_client_reply_ko(qc, "Unable to obtain video frame from the camera");
+ 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,
@@ -243,20 +1173,68 @@ _camera_client_recv(void* opaque,
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;
- // TODO: implement!
+ /*
+ * 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.
- */
+/* Emulated camera client has been disconnected from the service. */
static void
-_camera_client_close(void* opaque)
+_camera_client_close(void* opaque)
{
CameraClient* cc = (CameraClient*)opaque;
- D("Camera client closed: name=%s, inp_channel=%d",
- cc->name, cc->inp_channel);
+ 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);
}
@@ -284,6 +1262,8 @@ _camera_service_connect(void* opaque,
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,
@@ -308,15 +1288,16 @@ android_camera_service_init(void)
static int _inited = 0;
if (!_inited) {
- _csDesc_init(&_camera_service_desc);
+ _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("could not register '%s' service", SERVICE_NAME);
+ derror("%s: Could not register '%s' service",
+ __FUNCTION__, SERVICE_NAME);
return;
}
- D("registered '%s' qemud service", SERVICE_NAME);
+ D("%s: Registered '%s' qemud service", __FUNCTION__, SERVICE_NAME);
}
}