From 8dd31e8e10fc3ca10192368acf19d2345eeddde7 Mon Sep 17 00:00:00 2001 From: Vladimir Chtchetkine Date: Wed, 15 Feb 2012 17:16:04 -0800 Subject: Multi-touch emulation support Change-Id: I311dc55fe10f41637775baa330a7c732ff317f51 --- android/android-device.c | 74 ++++-- android/avd/hardware-properties.ini | 7 + android/cmdline-options.h | 2 + android/help.c | 15 ++ android/main.c | 24 ++ android/multitouch-port.c | 464 ++++++++++++++++++++++++++++++++++++ android/multitouch-port.h | 132 ++++++++++ android/multitouch-screen.c | 422 ++++++++++++++++++++++++++++++++ android/multitouch-screen.h | 68 ++++++ android/qemulator.c | 3 +- android/utils/debug.h | 2 + android/utils/jpeg-compress.c | 197 +++++++++++++++ android/utils/jpeg-compress.h | 93 ++++++++ 13 files changed, 1479 insertions(+), 24 deletions(-) create mode 100644 android/multitouch-port.c create mode 100644 android/multitouch-port.h create mode 100644 android/multitouch-screen.c create mode 100644 android/multitouch-screen.h create mode 100644 android/utils/jpeg-compress.c create mode 100644 android/utils/jpeg-compress.h (limited to 'android') diff --git a/android/android-device.c b/android/android-device.c index 37af0c6..498e304 100644 --- a/android/android-device.c +++ b/android/android-device.c @@ -581,35 +581,60 @@ _android_dev_socket_recv(AndroidDevSocket* ads, char* buf, int bufsize) return -1; } - iolooper_add_read(_ads_io_looper(ads), ads->fd); - do { - int res = socket_recv(ads->fd, buf + recvd, bufsize - recvd); - if (res == 0) { - /* Disconnection. */ - errno = ECONNRESET; - recvd = -1; - break; + /* XXX: This is a hack that implements a blocked line read on an async + * event socket! Redo this ASAP! */ + if (ads->type == ADS_TYPE_EVENT) { + AndroidEventSocket* adsevent = (AndroidEventSocket*)ads; + asyncLineReader_init(&adsevent->alr, buf, bufsize, adsevent->io); + /* Default EOL for the line reader was '\n'. */ + asyncLineReader_setEOL(&adsevent->alr, '\0'); + AsyncStatus status = ASYNC_NEED_MORE; + + while (status == ASYNC_NEED_MORE) { + status = asyncLineReader_read(&adsevent->alr); + if (status == ASYNC_COMPLETE) { + recvd = adsevent->alr.pos; + break; + } else if (status == ASYNC_ERROR) { + if (errno == ENOMEM) { + recvd = adsevent->alr.pos; + } else { + recvd = -1; + } + break; + } } - - if (res < 0) { - if (errno == EINTR) { - /* loop on EINTR */ - continue; + } else { + iolooper_add_read(_ads_io_looper(ads), ads->fd); + do { + int res = socket_recv(ads->fd, buf + recvd, bufsize - recvd); + if (res == 0) { + /* Disconnection. */ + errno = ECONNRESET; + recvd = -1; + break; } - if (errno == EWOULDBLOCK || errno == EAGAIN) { - res = iolooper_wait_absolute(_ads_io_looper(ads), ads->deadline); - if (res > 0) { - /* Ready to read. */ + if (res < 0) { + if (errno == EINTR) { + /* loop on EINTR */ continue; } + + if (errno == EWOULDBLOCK || errno == EAGAIN) { + res = iolooper_wait_absolute(_ads_io_looper(ads), ads->deadline); + if (res > 0) { + /* Ready to read. */ + continue; + } + } + recvd = -1; + break; } - recvd = -1; - break; - } - recvd += res; - } while (recvd < bufsize); - iolooper_del_read(_ads_io_looper(ads), ads->fd); + recvd += res; + } while (recvd < bufsize); + iolooper_del_read(_ads_io_looper(ads), ads->fd); + } /* In case of an I/O failure we have to invoke failure callback. Note that we * report I/O failures only on registered sockets. */ @@ -1108,6 +1133,7 @@ _on_event_socket_io(void* opaque, int fd, unsigned events) /* Continue reading data. */ status = asyncLineReader_read(&adsevent->alr); if (status == ASYNC_COMPLETE) { + errno = 0; _on_event_received(adsevent); } else if (status == ASYNC_ERROR) { D("I/O failure while reading from channel '%s'@%d: %s", @@ -1153,6 +1179,7 @@ _on_event_socket_io(void* opaque, int fd, unsigned events) return; } else if (sent == to_send->data_remaining) { /* All data is sent. */ + errno = 0; adsevent->send_pending = to_send->next; _async_send_buffer_complete(to_send, ATR_SUCCESS); } else { @@ -1182,6 +1209,7 @@ _on_event_socket_connected(AndroidEventSocket* adsevent, int failure) /* Complete event socket connection by identifying it as "event" socket with * the application. */ res = _android_dev_socket_register(ads); + if (res) { const int save_error = errno; _android_event_socket_disconnect(adsevent); diff --git a/android/avd/hardware-properties.ini b/android/avd/hardware-properties.ini index ebf2450..4859680 100644 --- a/android/avd/hardware-properties.ini +++ b/android/avd/hardware-properties.ini @@ -53,6 +53,13 @@ default = yes abstract = Touch-screen support description = Whether there is a touch screen or not on the device. +# Multi-touch screen support +name = hw.multiTouch +type = boolean +default = no +abstract = Multi-touch screen support +description = Whether there is a multi-touch screen or not on the device. + # Hardware main keys (back/home) name = hw.mainKeys type = boolean diff --git a/android/cmdline-options.h b/android/cmdline-options.h index 0cf358f..d490e40 100644 --- a/android/cmdline-options.h +++ b/android/cmdline-options.h @@ -159,6 +159,8 @@ OPT_PARAM( gpu, "", "set hardware OpenGLES emulation mode" ) OPT_PARAM( fake_camera, "", "set fake camera emulation mode" ) OPT_LIST( webcam, "name=[,dir=]", "setup web camera emulation" ) +OPT_PARAM( screen, "", "set emulated screen mode" ) + #undef CFG_FLAG #undef CFG_PARAM #undef OPT_FLAG diff --git a/android/help.c b/android/help.c index 5f151ca..db46d21 100644 --- a/android/help.c +++ b/android/help.c @@ -1488,6 +1488,21 @@ help_webcam(stralloc_t* out) ); } +static void +help_screen(stralloc_t* out) +{ + PRINTF( + " Use -screen to set the emulated screen mode.\n" + " Valid values for are:\n\n" + + " touch -> emulate a touch screen\n" + " multi-touch -> emulate a multi-touch screen\n" + " off -> disable touch and multi-touch screen emulation\n\n" + + " Default mode for screen emulation is 'touch'.\n\n" + ); +} + #define help_no_skin NULL #define help_netspeed help_shaper #define help_netdelay help_shaper diff --git a/android/main.c b/android/main.c index b7421a1..2723834 100644 --- a/android/main.c +++ b/android/main.c @@ -1271,6 +1271,30 @@ int main(int argc, char **argv) args[n++] = "socket,vlan=1,mcast=230.0.0.10:1234"; } + /* Setup screen emulation */ + if (opts->screen) { + if (!strcmp(opts->screen, "touch")) { + hw->hw_touchScreen = 1; + hw->hw_multiTouch = 0; + } else if (!strcmp(opts->screen, "multi-touch")) { + hw->hw_multiTouch = 1; + hw->hw_touchScreen = 0; + } else if (!strcmp(opts->screen, "off")) { + hw->hw_touchScreen = 0; + hw->hw_multiTouch = 0; + } else { + derror("Invalid value for -screen parameter: %s\n", opts->screen); + derror("Valid values are: touch, multi-touch, or off\n"); + exit(1); + } + } else { + /* If both, touch and multitouch are set in hw.ini, choose multi-touch + * for screen emulation. */ + if (hw->hw_touchScreen && hw->hw_multiTouch) { + hw->hw_touchScreen = 0; + } + } + while(argc-- > 0) { args[n++] = *argv++; } diff --git a/android/multitouch-port.c b/android/multitouch-port.c new file mode 100644 index 0000000..c8e3ca5 --- /dev/null +++ b/android/multitouch-port.c @@ -0,0 +1,464 @@ +/* + * 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. + */ + +#include "qemu-common.h" +#include "utils/panic.h" +#include "android/hw-events.h" +#include "android/charmap.h" +#include "android/multitouch-screen.h" +#include "android/multitouch-port.h" +#include "android/globals.h" /* for android_hw */ +#include "android/utils/misc.h" +#include "android/utils/jpeg-compress.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(mtport,__VA_ARGS__) +#define D_ACTIVE VERBOSE_CHECK(mtport) + +/* Query timeout in milliseconds. */ +#define MTSP_QUERY_TIMEOUT 3000 +#define MTSP_MAX_MSG 2048 +#define MTSP_MAX_EVENT 2048 + +/* Multi-touch port descriptor. */ +struct AndroidMTSPort { + /* Caller identifier. */ + void* opaque; + /* Connected android device. */ + AndroidDevice* device; + /* Initialized JPEG compressor instance. */ + AJPEGDesc* jpeg_compressor; + /* Connection status: 1 connected, 0 - disconnected. */ + int is_connected; + /* Buffer where to receive multitouch messages. */ + char mts_msg[MTSP_MAX_MSG]; + /* Buffer where to receive multitouch events. */ + char events[MTSP_MAX_EVENT]; +}; + +/* Destroys and frees the descriptor. */ +static void +_mts_port_free(AndroidMTSPort* mtsp) +{ + if (mtsp != NULL) { + if (mtsp->jpeg_compressor != NULL) { + jpeg_compressor_destroy(mtsp->jpeg_compressor); + } + if (mtsp->device != NULL) { + android_device_destroy(mtsp->device); + } + AFREE(mtsp); + } +} + +/******************************************************************************** + * Multi-touch action handlers + *******************************************************************************/ + +/* + * Although there are a lot of similarities in the way the handlers below are + * implemented, for the sake of tracing / debugging it's better to have a + * separate handler for each distinctive action. + */ + +/* First pointer down event handler. */ +static void +_on_action_down(int tracking_id, int x, int y, int pressure) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, x, y, pressure); +} + +/* Last pointer up event handler. */ +static void +_on_action_up(int tracking_id) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, 0, 0, 0); +} + +/* Pointer down event handler. */ +static void +_on_action_pointer_down(int tracking_id, int x, int y, int pressure) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, x, y, pressure); +} + +/* Pointer up event handler. */ +static void +_on_action_pointer_up(int tracking_id) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, 0, 0, 0); +} + +/* Pointer move event handler. */ +static void +_on_action_move(int tracking_id, int x, int y, int pressure) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, x, y, pressure); +} + +/******************************************************************************** + * Multi-touch event handlers + *******************************************************************************/ + +/* Handles "pointer move" event. */ +static void +_on_move(const char* param) +{ + const char* pid = param; + D(">>> MOVE: %s", param); + while (pid && *pid) { + int pid_val, x, y, pressure = 0; + if (!get_token_value_int(pid, "pid", &pid_val) && + !get_token_value_int(pid, "x", &x) && + !get_token_value_int(pid, "y", &y)) { + get_token_value_int(pid, "pressure", &pressure); + _on_action_move(pid_val, x, y, pressure); + pid = strstr(pid + 1, "pid"); + } else { + break; + } + } +} + +/* Handles "first pointer down" event. */ +static void +_on_down(const char* param) +{ + int pid_val, x, y, pressure = 0; + D(">>> 1-ST DOWN: %s", param); + if (!get_token_value_int(param, "pid", &pid_val) && + !get_token_value_int(param, "x", &x) && + !get_token_value_int(param, "y", &y)) { + get_token_value_int(param, "pressure", &pressure); + _on_action_down(pid_val, x, y, pressure); + } else { + W("Invalid parameters '%s' for MTS 'down' event", param); + } +} + +/* Handles "last pointer up" event. */ +static void +_on_up(const char* param) +{ + int pid_val; + D(">>> LAST UP: %s", param); + if (!get_token_value_int(param, "pid", &pid_val)) { + _on_action_up(pid_val); + } else { + W("Invalid parameters '%s' for MTS 'up' event", param); + } +} + +/* Handles "next pointer down" event. */ +static void +_on_pdown(const char* param) +{ + int pid_val, x, y, pressure = 0; + D(">>> DOWN: %s", param); + if (!get_token_value_int(param, "pid", &pid_val) && + !get_token_value_int(param, "x", &x) && + !get_token_value_int(param, "y", &y)) { + get_token_value_int(param, "pressure", &pressure); + _on_action_pointer_down(pid_val, x, y, pressure); + } else { + W("Invalid parameters '%s' for MTS 'pointer down' event", param); + } +} + +/* Handles "next pointer up" event. */ +static void +_on_pup(const char* param) +{ + int pid_val; + D(">>> UP: %s", param); + if (!get_token_value_int(param, "pid", &pid_val)) { + _on_action_pointer_up(pid_val); + } else { + W("Invalid parameters '%s' for MTS 'up' event", param); + } +} + +/******************************************************************************** + * Device communication callbacks + *******************************************************************************/ + +/* Main event handler. + * This routine is invoked when an event message has been received from the + * device. + */ +static void +_on_event_received(void* opaque, AndroidDevice* ad, char* msg, int msgsize) +{ + char* action; + int res; + AndroidMTSPort* mtsp = (AndroidMTSPort*)opaque; + + if (errno) { + D("Multi-touch notification has failed: %s", strerror(errno)); + return; + } + + /* Dispatch the event to an appropriate handler. */ + res = get_token_value_alloc(msg, "action", &action); + if (!res) { + const char* param = strchr(msg, ' '); + if (param) { + param++; + } + if (!strcmp(action, "move")) { + _on_move(param); + } else if (!strcmp(action, "down")) { + _on_down(param); + } else if (!strcmp(action, "up")) { + _on_up(param); + } else if (!strcmp(action, "pdown")) { + _on_pdown(param); + } else if (!strcmp(action, "pup")) { + _on_pup(param); + } else { + D("Unknown multi-touch event action '%s'", action); + } + free(action); + } + + /* Listen to the next event. */ + android_device_listen(ad, mtsp->events, sizeof(mtsp->events), + _on_event_received); +} + +/* A callback that is invoked when android device is connected (i.e. both, + * command and event channels have been established). + * Param: + * opaque - AndroidMTSPort instance. + * ad - Android device used by this port. + * failure - Connections status. + */ +static void +_on_device_connected(void* opaque, AndroidDevice* ad, int failure) +{ + if (!failure) { + AndroidMTSPort* mtsp = (AndroidMTSPort*)opaque; + mtsp->is_connected = 1; + D("Multi-touch emulation has started"); + android_device_listen(mtsp->device, mtsp->events, sizeof(mtsp->events), + _on_event_received); + mts_port_start(mtsp); + } +} + +/* Invoked when an I/O failure occurs on a socket. + * Note that this callback will not be invoked on connection failures. + * Param: + * opaque - AndroidMTSPort instance. + * ad - Android device instance + * ads - Connection socket where failure has occured. + * failure - Contains 'errno' indicating the reason for failure. + */ +static void +_on_io_failure(void* opaque, AndroidDevice* ad, int failure) +{ + AndroidMTSPort* mtsp = (AndroidMTSPort*)opaque; + E("Multi-touch port got disconnected: %s", strerror(failure)); + mtsp->is_connected = 0; + android_device_disconnect(ad); + + /* Try to reconnect again. */ + android_device_connect_async(ad, _on_device_connected); +} + +/******************************************************************************** + * MTS port API + *******************************************************************************/ + +AndroidMTSPort* +mts_port_create(void* opaque) +{ + AndroidMTSPort* mtsp; + int res; + + ANEW0(mtsp); + mtsp->opaque = opaque; + mtsp->is_connected = 0; + + /* Initialize default MTS descriptor. */ + multitouch_init(mtsp); + + /* Create JPEG compressor. Put "$BLOB:%09d\0" + MTFrameHeader header in front + * of the compressed data. this way we will have entire query ready to be + * transmitted to the device. */ + mtsp->jpeg_compressor = jpeg_compressor_create(16 + sizeof(MTFrameHeader), 4096); + + mtsp->device = android_device_init(mtsp, AD_MULTITOUCH_PORT, _on_io_failure); + if (mtsp->device == NULL) { + _mts_port_free(mtsp); + return NULL; + } + + res = android_device_connect_async(mtsp->device, _on_device_connected); + if (res != 0) { + mts_port_destroy(mtsp); + return NULL; + } + + return mtsp; +} + +void +mts_port_destroy(AndroidMTSPort* mtsp) +{ + _mts_port_free(mtsp); +} + +int +mts_port_is_connected(AndroidMTSPort* mtsp) +{ + return mtsp->is_connected; +} + +int +mts_port_start(AndroidMTSPort* mtsp) +{ + char qresp[MTSP_MAX_MSG]; + char query[256]; + AndroidHwConfig* config = android_hw; + + /* Query the device to start capturing multi-touch events, also providing + * the device with width / height of the emulator's screen. This is required + * so device can properly adjust multi-touch event coordinates, and display + * emulator's framebuffer. */ + snprintf(query, sizeof(query), "start:%dx%d", + config->hw_lcd_width, config->hw_lcd_height); + int res = android_device_query(mtsp->device, query, qresp, sizeof(qresp), + MTSP_QUERY_TIMEOUT); + if (!res) { + /* By protocol device should reply with its view dimensions. */ + if (*qresp) { + int width, height; + if (sscanf(qresp, "%dx%d", &width, &height) == 2) { + multitouch_set_device_screen_size(width, height); + D("Multi-touch emulation has started. Device dims: %dx%d", + width, height); + } else { + E("Unexpected reply to MTS 'start' query: %s", qresp); + android_device_query(mtsp->device, "stop", qresp, sizeof(qresp), + MTSP_QUERY_TIMEOUT); + res = -1; + } + } else { + E("MTS protocol error: no reply to query 'start'"); + android_device_query(mtsp->device, "stop", qresp, sizeof(qresp), + MTSP_QUERY_TIMEOUT); + res = -1; + } + } else { + if (errno) { + D("Query 'start' failed on I/O: %s", strerror(errno)); + } else { + D("Query 'start' failed on device: %s", qresp); + } + } + return res; +} + +int +mts_port_stop(AndroidMTSPort* mtsp) +{ + char qresp[MTSP_MAX_MSG]; + const int res = + android_device_query(mtsp->device, "stop", qresp, sizeof(qresp), + MTSP_QUERY_TIMEOUT); + if (res) { + if (errno) { + D("Query 'stop' failed on I/O: %s", strerror(errno)); + } else { + D("Query 'stop' failed on device: %s", qresp); + } + } + + return res; +} + +/******************************************************************************** + * Handling framebuffer updates + *******************************************************************************/ + +/* Compresses a framebuffer region into JPEG image. + * Param: + * mtsp - Multi-touch port descriptor with initialized JPEG compressor. + * fmt Descriptor for framebuffer region to compress. + * fb Beginning of the framebuffer. + * jpeg_quality JPEG compression quality. A number from 1 to 100. Note that + * value 10 provides pretty decent image for the purpose of multi-touch + * emulation. + */ +static void +_fb_compress(const AndroidMTSPort* mtsp, + const MTFrameHeader* fmt, + const uint8_t* fb, + int jpeg_quality) +{ + jpeg_compressor_compress_fb(mtsp->jpeg_compressor, fmt->x, fmt->y, fmt->w, + fmt->h, fmt->bpp, fmt->bpl, fb, jpeg_quality); +} + +int +mts_port_send_frame(AndroidMTSPort* mtsp, + MTFrameHeader* fmt, + const uint8_t* fb, + async_send_cb cb, + void* cb_opaque) +{ + char* query; + int blob_size, off; + + /* Make sure that port is connected. */ + if (!mts_port_is_connected(mtsp)) { + return -1; + } + + /* Compress framebuffer region. 10% quality seems to be sufficient. */ + fmt->format = MTFB_JPEG; + _fb_compress(mtsp, fmt, fb, 10); + + /* Total size of the blob: header + JPEG image. */ + blob_size = sizeof(MTFrameHeader) + + jpeg_compressor_get_jpeg_size(mtsp->jpeg_compressor); + + /* Query starts at the beginning of the buffer allocated by the compressor's + * destination manager. */ + query = (char*)jpeg_compressor_get_buffer(mtsp->jpeg_compressor); + + /* Build the $BLOB query to transfer to the device. */ + snprintf(query, jpeg_compressor_get_header_size(mtsp->jpeg_compressor), + "$BLOB:%09d", blob_size); + off = strlen(query) + 1; + + /* Copy framebuffer update header to the query. */ + memcpy(query + off, fmt, sizeof(MTFrameHeader)); + + /* Zeroing the rectangle in the update header we indicate that it contains + * no updates. */ + fmt->x = fmt->y = fmt->w = fmt->h = 0; + + /* Initiate asynchronous transfer of the updated framebuffer rectangle. */ + if (android_device_send_async(mtsp->device, query, off + blob_size, 0, cb, cb_opaque)) { + D("Unable to send query '%s': %s", query, strerror(errno)); + return -1; + } + + return 0; +} diff --git a/android/multitouch-port.h b/android/multitouch-port.h new file mode 100644 index 0000000..553617b --- /dev/null +++ b/android/multitouch-port.h @@ -0,0 +1,132 @@ +/* + * 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. + */ + +#ifndef ANDROID_ANDROID_MULTITOUCH_PORT_H_ +#define ANDROID_ANDROID_MULTITOUCH_PORT_H_ + +/* + * Encapsulates exchange protocol between the multi-touch screen emulator, and an + * application running on an Android device that provides touch events, and is + * connected to the host via USB. + */ + +#include "android/android-device.h" + +/* TCP port reserved for multi-touch emulation. */ +#define AD_MULTITOUCH_PORT 1969 + +/* + * Codes that define transmitted framebuffer format: + * + * NOTE: Application on the device side depends on these values. Any changes + * made here must be reflected in the app too. Application location is at + * 'sdk/apps/SdkController/SdkControllerMultitouch' root. + */ + +/* Framebuffer is transmitted as JPEG. */ +#define MTFB_JPEG 1 +/* Framebuffer is transmitted as raw RGB565 bitmap. */ +#define MTFB_RGB565 2 +/* Framebuffer is transmitted as raw RGB888 bitmap. */ +#define MTFB_RGB888 3 + +/* Framebuffer update descriptor. + * This descriptor is used to collect properties of the updated framebuffer + * region. This descriptor is also sent to the MT emulation application on the + * device, so it can properly redraw its screen. + * + * NOTE: Application on the device side depends on that structure. Any changes + * made here must be reflected in the app too. Application location is at + * 'sdk/apps/SdkController/SdkControllerMultitouch' root. + */ +typedef struct MTFrameHeader { + /* Size of the header. Must be always sizeof(MTFrameHeader). */ + int header_size; + /* Display width */ + int disp_width; + /* Display height */ + int disp_height; + /* x, y, w, and h define framebuffer region that has been updated. */ + int x; + int y; + int w; + int h; + /* Bytes per line in the framebufer. */ + int bpl; + /* Bytes per pixel in the framebufer. */ + int bpp; + /* Defines format in which framebuffer is transmitted to the device. */ + int format; +} MTFrameHeader; + +/* Declares multi-touch port descriptor. */ +typedef struct AndroidMTSPort AndroidMTSPort; + +/* Creates multi-touch port, and connects it to the device. + * Param: + * opaque - An opaque pointer that is passed back to the callback routines. + * Return: + * Initialized device descriptor on success, or NULL on failure. If failure is + * returned from this routine, 'errno' indicates the reason for failure. If this + * routine successeds, a connection is established with the sensor reading + * application on the device. + */ +extern AndroidMTSPort* mts_port_create(void* opaque); + +/* Disconnects from the multi-touch port, and destroys the descriptor. */ +extern void mts_port_destroy(AndroidMTSPort* amtp); + +/* Checks if port is connected to a MT-emulating application on the device. + * Note that connection can go out and then be restored at any time after + * mts_port_create API succeeded. + */ +extern int mts_port_is_connected(AndroidMTSPort* amtp); + +/* Queries the connected application to start delivering multi-touch events. + * Param: + * amtp - Android multi-touch port instance returned from mts_port_create. + * Return: + * Zero on success, failure otherwise. + */ +extern int mts_port_start(AndroidMTSPort* amtp); + +/* Queries the connected application to stop delivering multi-touch events. + * Param: + * amtp - Android multi-touch port instance returned from mts_port_create. + * Return: + * Zero on success, failure otherwise. + */ +extern int mts_port_stop(AndroidMTSPort* amtp); + +/* Sends framebuffer update to the multi-touch emulation application, running on + * the android device. + * Param: + * mtsp - Android multi-touch port instance returned from mts_port_create. + * fmt - Framebuffer update descriptor. + * fb - Beginning of the framebuffer. + * cb - Callback to invoke when update has been transferred to the MT-emulating + * application on the device. + * cb_opaque - An opaque parameter to pass back to the 'cb' callback. + * Return: + * 0 on success, or != 0 on failure. + */ +extern int mts_port_send_frame(AndroidMTSPort* mtsp, + MTFrameHeader* fmt, + const uint8_t* fb, + async_send_cb cb, + void* cb_opaque); + +#endif /* ANDROID_ANDROID_MULTITOUCH_PORT_H_ */ diff --git a/android/multitouch-screen.c b/android/multitouch-screen.c new file mode 100644 index 0000000..e1a9a8a --- /dev/null +++ b/android/multitouch-screen.c @@ -0,0 +1,422 @@ +/* + * 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. + */ + +#include "qemu-common.h" +#include "user-events.h" +#include "android/display-core.h" +#include "android/hw-events.h" +#include "android/charmap.h" +#include "android/globals.h" /* for android_hw */ +#include "android/utils/misc.h" +#include "android/utils/debug.h" +#include "android/multitouch-screen.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(mtscreen,__VA_ARGS__) +#define D_ACTIVE VERBOSE_CHECK(mtscreen) + +/* Maximum number of pointers, supported by multi-touch emulation. */ +#define MTS_POINTERS_NUM 10 +/* Signals that pointer is not tracked (or is "up"). */ +#define MTS_POINTER_UP -1 +/* Special tracking ID for a mouse pointer. */ +#define MTS_POINTER_MOUSE -2 + +/* Describes state of a multi-touch pointer */ +typedef struct MTSPointerState { + /* Tracking ID assigned to the pointer by an app emulating multi-touch. */ + int tracking_id; + /* X - coordinate of the tracked pointer. */ + int x; + /* Y - coordinate of the tracked pointer. */ + int y; + /* Current pressure value. */ + int pressure; +} MTSPointerState; + +/* Describes state of an emulated multi-touch screen. */ +typedef struct MTSState { + /* Multi-touch port connected to the device. */ + AndroidMTSPort* mtsp; + /* Emulator's display state. */ + DisplayState* ds; + /* Screen width of the device that emulates multi-touch. */ + int device_width; + /* Screen height of the device that emulates multi-touch. */ + int device_height; + /* Number of tracked pointers. */ + int tracked_ptr_num; + /* Index in the 'tracked_pointers' array of the last pointer for which + * ABS_MT_SLOT was sent. -1 indicates that no slot selection has been made + * yet. */ + int current_slot; + /* Accumulator for ABS_TOUCH_MAJOR value. */ + int touch_major; + /* Array of multi-touch pointers. */ + MTSPointerState tracked_pointers[MTS_POINTERS_NUM]; + /* Header collecting framebuffer information and updates. */ + MTFrameHeader fb_header; + /* Boolean value indicating if framebuffer updates are currently being + * transferred to the application running on the device. */ + int fb_transfer_in_progress; +} MTSState; + +/* Default multi-touch screen descriptor */ +static MTSState _MTSState; + +/* Pushes event to the event device. */ +static void +_push_event(int type, int code, int value) +{ + user_event_generic(type, code, value); +} + +/* Gets an index in the MTS's tracking pointers array MTS for the given + * tracking id. + * Return: + * Index of a matching entry in the MTS's tracking pointers array, or -1 if + * matching entry was not found. + */ +static int +_mtsstate_get_pointer_index(const MTSState* mts_state, int tracking_id) +{ + int index; + for (index = 0; index < MTS_POINTERS_NUM; index++) { + if (mts_state->tracked_pointers[index].tracking_id == tracking_id) { + return index; + } + } + return -1; +} + +/* Gets an index of the first untracking pointer in the MTS's tracking pointers + * array. + * Return: + * An index of the first untracking pointer, or -1 if all pointers are tracked. + */ +static int +_mtsstate_get_available_pointer_index(const MTSState* mts_state) +{ + return _mtsstate_get_pointer_index(mts_state, MTS_POINTER_UP); +} + +/* Handles a "pointer down" event + * Param: + * mts_state - MTS state descriptor. + * tracking_id - Tracking ID of the "downed" pointer. + * x, y - "Downed" pointer coordinates, + * pressure - Pressure value for the pointer. + */ +static void +_mts_pointer_down(MTSState* mts_state, int tracking_id, int x, int y, int pressure) +{ + /* Get first available slot for the new pointer. */ + const int slot_index = _mtsstate_get_available_pointer_index(mts_state); + + /* Make sure there is a place for the pointer. */ + if (slot_index >= 0) { + /* Initialize pointer's entry. */ + mts_state->tracked_ptr_num++; + mts_state->tracked_pointers[slot_index].tracking_id = tracking_id; + mts_state->tracked_pointers[slot_index].x = x; + mts_state->tracked_pointers[slot_index].y = y; + mts_state->tracked_pointers[slot_index].pressure = pressure; + + /* Send events indicating a "pointer down" to the EventHub */ + /* Make sure that correct slot is selected. */ + if (slot_index != mts_state->current_slot) { + _push_event(EV_ABS, ABS_MT_SLOT, slot_index); + } + _push_event(EV_ABS, ABS_MT_TRACKING_ID, slot_index); + _push_event(EV_ABS, ABS_MT_TOUCH_MAJOR, ++mts_state->touch_major); + _push_event(EV_ABS, ABS_MT_PRESSURE, pressure); + _push_event(EV_ABS, ABS_MT_POSITION_X, x); + _push_event(EV_ABS, ABS_MT_POSITION_Y, y); + _push_event(EV_SYN, SYN_REPORT, 0); + mts_state->current_slot = slot_index; + } else { + D("MTS pointer count is exceeded."); + return; + } +} + +/* Handles a "pointer up" event + * Param: + * mts_state - MTS state descriptor. + * slot_index - Pointer's index in the MTS's array of tracked pointers. + */ +static void +_mts_pointer_up(MTSState* mts_state, int slot_index) +{ + /* Make sure that correct slot is selected. */ + if (slot_index != mts_state->current_slot) { + _push_event(EV_ABS, ABS_MT_SLOT, slot_index); + } + + /* Send event indicating "pointer up" to the EventHub. */ + _push_event(EV_ABS, ABS_MT_TRACKING_ID, -1); + _push_event(EV_SYN, SYN_REPORT, 0); + + /* Update MTS descriptor, removing the tracked pointer. */ + mts_state->tracked_pointers[slot_index].tracking_id = MTS_POINTER_UP; + mts_state->tracked_pointers[slot_index].x = 0; + mts_state->tracked_pointers[slot_index].y = 0; + mts_state->tracked_pointers[slot_index].pressure = 0; + + /* Since current slot is no longer tracked, make sure we will do a "select" + * next time we send events to the EventHub. */ + mts_state->current_slot = -1; + mts_state->tracked_ptr_num--; + assert(mts_state->tracked_ptr_num >= 0); +} + +/* Handles a "pointer move" event + * Param: + * mts_state - MTS state descriptor. + * slot_index - Pointer's index in the MTS's array of tracked pointers. + * x, y - New pointer coordinates, + * pressure - Pressure value for the pointer. + */ +static void +_mts_pointer_move(MTSState* mts_state, int slot_index, int x, int y, int pressure) +{ + MTSPointerState* ptr_state = &mts_state->tracked_pointers[slot_index]; + + /* Make sure that coordinates have really changed. */ + if (ptr_state->x == x && ptr_state->y == y) { + /* Coordinates didn't change. Bail out. */ + return; + } + + /* Make sure that the right slot is selected. */ + if (slot_index != mts_state->current_slot) { + _push_event(EV_ABS, ABS_MT_SLOT, slot_index); + mts_state->current_slot = slot_index; + } + + /* Push the changes down. */ + if (ptr_state->pressure != pressure && pressure != 0) { + _push_event(EV_ABS, ABS_MT_PRESSURE, pressure); + ptr_state->pressure = pressure; + } + if (ptr_state->x != x) { + _push_event(EV_ABS, ABS_MT_POSITION_X, x); + ptr_state->x = x; + } + if (ptr_state->y != y) { + _push_event(EV_ABS, ABS_MT_POSITION_Y, y); + ptr_state->y = y; + } + _push_event(EV_SYN, SYN_REPORT, 0); +} + +/******************************************************************************** + * Multi-touch API + *******************************************************************************/ + +/* Callback that is invoked when framebuffer update has been transmitted to the + * device. */ +static void +_on_fb_sent(void* opaque, ATResult res, void* data, int size, int sent) +{ + MTSState* const mts_state = (MTSState*)opaque; + + /* Lets see if we have accumulated more changes while transmission has been + * in progress. */ + if (mts_state->fb_header.w && mts_state->fb_header.h) { + /* Send accumulated updates. */ + if (mts_port_send_frame(mts_state->mtsp, &mts_state->fb_header, + mts_state->ds->surface->data, _on_fb_sent, + mts_state)) { + mts_state->fb_transfer_in_progress = 0; + } + } else { + /* Framebuffer transfer is completed, and no more updates are pending. */ + mts_state->fb_transfer_in_progress = 0; + } +} + +/* A callback invoked on framebuffer updates. + * Param: + * opaque - MTSState instance. + * x, y, w, h - Defines an updated rectangle inside the framebuffer. + */ +static void +_mt_fb_update(void* opaque, int x, int y, int w, int h) +{ + MTSState* const mts_state = (MTSState*)opaque; + const DisplaySurface* const surface = mts_state->ds->surface; + + if (mts_state->fb_header.w == 0 && mts_state->fb_header.h == 0) { + /* First update after previous one has been transmitted to the device. */ + mts_state->fb_header.x = x; + mts_state->fb_header.y = y; + mts_state->fb_header.w = w; + mts_state->fb_header.h = h; + } else { + /* + * Accumulate framebuffer changes in the header. + */ + + /* "right" and "bottom" coordinates of the current update. */ + int right = mts_state->fb_header.x + mts_state->fb_header.w; + int bottom = mts_state->fb_header.y + mts_state->fb_header.h; + + /* "right" and "bottom" coordinates of the new update. */ + const int new_right = x + w; + const int new_bottom = y + h; + + /* Accumulate changed rectangle coordinates in the header. */ + if (mts_state->fb_header.x > x) { + mts_state->fb_header.x = x; + } + if (mts_state->fb_header.y > y) { + mts_state->fb_header.y = y; + } + if (right < new_right) { + right = new_right; + } + if (bottom < new_bottom) { + bottom = new_bottom; + } + mts_state->fb_header.w = right - mts_state->fb_header.x; + mts_state->fb_header.h = bottom - mts_state->fb_header.y; + } + + /* TODO: Looks like general framebuffer properties can change on the fly. + * Find a callback that can catch that. For now, just copy FB properties + * over in every FB update. */ + mts_state->fb_header.bpp = surface->pf.bytes_per_pixel; + mts_state->fb_header.bpl = surface->linesize; + mts_state->fb_header.disp_width = surface->width; + mts_state->fb_header.disp_height = surface->height; + + /* We will send updates to the device only after previous transmission is + * completed. */ + if (!mts_state->fb_transfer_in_progress) { + mts_state->fb_transfer_in_progress = 1; + if (mts_port_send_frame(mts_state->mtsp, &mts_state->fb_header, + surface->data, _on_fb_sent, mts_state)) { + mts_state->fb_transfer_in_progress = 0; + } + } +} + +void +multitouch_init(AndroidMTSPort* mtsp) +{ + /* Multi-touch service initialization flag. */ + static int _is_mt_initialized = 0; + + if (!_is_mt_initialized) { + MTSState* const mts_state = &_MTSState; + DisplayState* const ds = get_displaystate(); + DisplayUpdateListener* dul; + int index; + + /* + * Initialize the descriptor. + */ + + memset(mts_state, 0, sizeof(MTSState)); + mts_state->tracked_ptr_num = 0; + mts_state->current_slot = -1; + for (index = 0; index < MTS_POINTERS_NUM; index++) { + mts_state->tracked_pointers[index].tracking_id = MTS_POINTER_UP; + } + mts_state->device_width = android_hw->hw_lcd_width; + mts_state->device_height = android_hw->hw_lcd_height; + mts_state->mtsp = mtsp; + mts_state->fb_header.header_size = sizeof(MTFrameHeader); + mts_state->fb_transfer_in_progress = 0; + + /* + * Set framebuffer update listener. + */ + + ANEW0(dul); + dul->opaque = &_MTSState; + dul->dpy_update = _mt_fb_update; + + /* Initialize framebuffer information in the screen descriptor. */ + mts_state->ds = ds; + mts_state->fb_header.disp_width = ds->surface->width; + mts_state->fb_header.disp_height = ds->surface->height; + mts_state->fb_header.x = mts_state->fb_header.y = 0; + mts_state->fb_header.w = mts_state->fb_header.h = 0; + mts_state->fb_header.bpp = ds->surface->pf.bytes_per_pixel; + mts_state->fb_header.bpl = ds->surface->linesize; + mts_state->fb_transfer_in_progress = 0; + + register_displayupdatelistener(ds, dul); + + _is_mt_initialized = 1; + } +} + +void +multitouch_update_pointer(MTESource source, + int tracking_id, + int x, + int y, + int pressure) +{ + MTSState* const mts_state = &_MTSState; + + /* Assign a fixed tracking ID to the mouse pointer. */ + if (source == MTES_MOUSE) { + tracking_id = MTS_POINTER_MOUSE; + } + + /* Find the tracked pointer for the tracking ID. */ + const int slot_index = _mtsstate_get_pointer_index(mts_state, tracking_id); + if (slot_index < 0) { + /* This is the first time the pointer is seen. Must be "pressed", + * otherwise it's "hoovering", which we don't support yet. */ + if (pressure == 0) { + if (tracking_id != MTS_POINTER_MOUSE) { + D("Unexpected MTS pointer update for tracking id: %d", + tracking_id); + } + return; + } + + /* This is a "pointer down" event */ + _mts_pointer_down(mts_state, tracking_id, x, y, pressure); + } else if (pressure == 0) { + /* This is a "pointer up" event */ + _mts_pointer_up(mts_state, slot_index); + } else { + /* This is a "pointer move" event */ + _mts_pointer_move(mts_state, slot_index, x, y, pressure); + } +} + +int +multitouch_get_max_slot() +{ + return MTS_POINTERS_NUM - 1; +} + +void +multitouch_set_device_screen_size(int width, int height) +{ + MTSState* const mts_state = &_MTSState; + + mts_state->device_width = width; + mts_state->device_height = height; +} diff --git a/android/multitouch-screen.h b/android/multitouch-screen.h new file mode 100644 index 0000000..9288073 --- /dev/null +++ b/android/multitouch-screen.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#ifndef ANDROID_MULTITOUCH_SCREEN_H_ +#define ANDROID_MULTITOUCH_SCREEN_H_ + +#include "android/multitouch-port.h" + +/* + * Encapsulates functionality of multi-touch screen. Main task of this component + * is to report touch events to the emulated system via event device (see + * hw/goldfish_events_device.c) The source of touch events can be a mouse, or an + * actual android device that is used for multi-touch emulation. Note that since + * we need to simultaneousely support a mouse and a device as event source, we + * need to know which one has sent us a touch event. This is important for proper + * tracking of pointer IDs when multitouch is in play. + */ + +/* Defines a source of multi-touch event. This is used to properly track + * pointer IDs. + */ +typedef enum MTESource { + /* The event is associated with a mouse. */ + MTES_MOUSE, + /* The event is associated with an actual android device. */ + MTES_DEVICE, +} MTESource; + +/* Initializes MTSState instance. + * Param: + * mtsp - Instance of the multi-touch port connected to the device. + */ +extern void multitouch_init(AndroidMTSPort* mtsp); + +/* Handles a MT pointer event. + * Param: + * source - Identifies the source of the event (mouse or a device). + * tracking_id - Tracking ID of the pointer. + * x, y - Pointer coordinates, + * pressure - Pressure value for the pointer. + */ +extern void multitouch_update_pointer(MTESource source, + int tracking_id, + int x, + int y, + int pressure); + +/* Gets maximum slot index available for the multi-touch emulation. */ +extern int multitouch_get_max_slot(); + +/* Saves screen size reported by the device that emulates multi-touch. */ +extern void multitouch_set_device_screen_size(int width, int height); + +#endif /* ANDROID_MULTITOUCH_SCREEN_H_ */ + diff --git a/android/qemulator.c b/android/qemulator.c index 35587ff..cd97510 100644 --- a/android/qemulator.c +++ b/android/qemulator.c @@ -81,7 +81,8 @@ qemulator_setup( QEmulator* emulator ) qemulator_set_title(emulator); - skin_window_enable_touch ( emulator->window, android_hw->hw_touchScreen != 0 ); + skin_window_enable_touch ( emulator->window, android_hw->hw_touchScreen != 0 || + android_hw->hw_multiTouch != 0); skin_window_enable_dpad ( emulator->window, android_hw->hw_dPad != 0 ); skin_window_enable_qwerty( emulator->window, android_hw->hw_keyboard != 0 ); skin_window_enable_trackball( emulator->window, android_hw->hw_trackBall != 0 ); diff --git a/android/utils/debug.h b/android/utils/debug.h index ec3094d..e97ec43 100644 --- a/android/utils/debug.h +++ b/android/utils/debug.h @@ -38,6 +38,8 @@ _VERBOSE_TAG(camera, "camera") \ _VERBOSE_TAG(adevice, "android device connected via port forwarding") \ _VERBOSE_TAG(sensors_port, "sensors emulator connected to android device") \ + _VERBOSE_TAG(mtport, "multi-touch emulator connected to android device") \ + _VERBOSE_TAG(mtscreen, "multi-touch screen emulation") \ _VERBOSE_TAG(gles, "hardware OpenGLES emulation") \ _VERBOSE_TAG(adbserver, "ADB server") \ _VERBOSE_TAG(adbclient, "ADB QEMU client") \ diff --git a/android/utils/jpeg-compress.c b/android/utils/jpeg-compress.c new file mode 100644 index 0000000..2b42a00 --- /dev/null +++ b/android/utils/jpeg-compress.c @@ -0,0 +1,197 @@ +/* Copyright (C) 2011 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ + +#include +#include "jinclude.h" +#include "jpeglib.h" +#include "jpeg-compress.h" +#include "panic.h" + +/* Implements JPEG destination manager's init_destination routine. */ +static void _on_init_destination(j_compress_ptr cinfo); +/* Implements JPEG destination manager's empty_output_buffer routine. */ +static boolean _on_empty_output_buffer(j_compress_ptr cinfo); +/* Implements JPEG destination manager's term_destination routine. */ +static void _on_term_destination(j_compress_ptr cinfo); + +/* JPEG compression descriptor. */ +struct AJPEGDesc { + /* Common JPEG compression destination manager header. */ + struct jpeg_destination_mgr common; + /* Buffer where to save compressed output. */ + uint8_t* jpeg_buf; + /* Byte size of the 'jpeg_buf' */ + int size; + /* Chunk size to increment the 'jpeg_buf' with on each allocation request. */ + int chunk_size; + /* Size of the header to put in front of the compressed data. */ + int header_size; +}; + +/******************************************************************************** + * jpeglib callbacks. + *******************************************************************************/ + +/* Implements JPEG destination manager's init_destination routine. */ +static void +_on_init_destination(j_compress_ptr cinfo) +{ + AJPEGDesc* const dst = (AJPEGDesc*)cinfo->dest; + if (dst->jpeg_buf == NULL) { + /* This is the first time our destination manager is initialized. + * Allocate minimal buffer. */ + dst->size = dst->chunk_size; + dst->jpeg_buf = malloc(dst->size); + if (dst->jpeg_buf == NULL) { + APANIC("Unable to allocate %d bytes for JPEG compression", dst->size); + } + } + /* Initialize common header with entire destination buffer. */ + dst->common.next_output_byte = dst->jpeg_buf + dst->header_size; + dst->common.free_in_buffer = dst->size - dst->header_size; +} + +/* Implements JPEG destination manager's empty_output_buffer routine. + * Name is a bit misleading here. This routine is called by the compressor when + * output buffer doesn't have enough free space to contain the next chunk of the + * compressed data. So, here we should reallocate the output buffer, rather than + * "empty" it. + */ +static boolean +_on_empty_output_buffer(j_compress_ptr cinfo) +{ + AJPEGDesc* const dst = (AJPEGDesc*)cinfo->dest; + /* Save already compressed data size. */ + const int accumulated = jpeg_compressor_get_jpeg_size(dst); + + /* Reallocate output buffer. */ + dst->size += dst->chunk_size; + dst->jpeg_buf = realloc(dst->jpeg_buf, dst->size); + if (dst->jpeg_buf == NULL) { + APANIC("Unable to allocate %d bytes for JPEG compression", dst->size); + } + + /* Update common header. */ + dst->common.next_output_byte = dst->jpeg_buf + accumulated + dst->header_size; + dst->common.free_in_buffer = dst->size - accumulated - dst->header_size; + + return TRUE; +} + +/* Implements JPEG destination manager's term_destination routine. + * We don't do anything here. All the cleanup will be performed when the user + * calls jpeg_compressor_destroy. */ +static void +_on_term_destination(j_compress_ptr cinfo) +{ +} + +/******************************************************************************** + * JPEG compressor API. + *******************************************************************************/ + +AJPEGDesc* +jpeg_compressor_create(int header_size, int chunk_size) +{ + AJPEGDesc* dsc = (AJPEGDesc*)malloc(sizeof(AJPEGDesc)); + if (dsc == NULL) { + APANIC("Unable to allocate JPEG compression descriptor."); + } + + dsc->common.next_output_byte = NULL; + dsc->common.free_in_buffer = 0; + dsc->common.init_destination = _on_init_destination; + dsc->common.empty_output_buffer = _on_empty_output_buffer; + dsc->common.term_destination = _on_term_destination; + dsc->jpeg_buf = NULL; + dsc->size = 0; + dsc->chunk_size = chunk_size; + dsc->header_size = header_size; + return dsc; +} + +void +jpeg_compressor_destroy(AJPEGDesc* dsc) +{ + if (dsc != NULL) { + if (dsc->jpeg_buf != NULL) { + free(dsc->jpeg_buf); + } + free(dsc); + } +} + +int +jpeg_compressor_get_jpeg_size(const AJPEGDesc* dsc) +{ + return (dsc->jpeg_buf == NULL) ? 0 : + (uint8_t*)dsc->common.next_output_byte - dsc->jpeg_buf - dsc->header_size; +} + +void* +jpeg_compressor_get_buffer(const AJPEGDesc* dsc) +{ + return dsc->jpeg_buf; +} + +int +jpeg_compressor_get_header_size(const AJPEGDesc* dsc) +{ + return dsc->header_size; +} + +void +jpeg_compressor_compress_fb(AJPEGDesc* dsc, + int x, int y, int w, int h, + int bpp, int bpl, + const uint8_t* fb, + int jpeg_quality){ + struct jpeg_compress_struct cinfo = {0}; + struct jpeg_error_mgr err_mgr; + + /* + * Initialize compressin information structure, and start compression + */ + + cinfo.err = jpeg_std_error(&err_mgr); + jpeg_create_compress(&cinfo); + cinfo.dest = &dsc->common; + cinfo.image_width = w; + cinfo.image_height = h; + + /* Decode framebuffer's pixel format. There can be only three: + * - RGB565, + * - RGBA8888, + * - RGBX8888 */ + if (bpp == 2) { + /* This is RGB565 - most commonly used pixel format for framebuffer. */ + cinfo.input_components = 2; + cinfo.in_color_space = JCS_RGB_565; + } else { + /* RGBA8888, or RGBX8888 - makes no difference here. */ + cinfo.input_components = 4; + cinfo.in_color_space = JCS_RGBA_8888; + } + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, jpeg_quality, TRUE); + jpeg_start_compress(&cinfo, TRUE); + + /* Line by line compress the region. */ + while (cinfo.next_scanline < cinfo.image_height) { + JSAMPROW rgb = (JSAMPROW)(fb + (cinfo.next_scanline + y) * bpl + x * bpp); + jpeg_write_scanlines(&cinfo, (JSAMPARRAY)&rgb, 1); + } + + /* Complete the compression. */ + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); +} diff --git a/android/utils/jpeg-compress.h b/android/utils/jpeg-compress.h new file mode 100644 index 0000000..4e0e61a --- /dev/null +++ b/android/utils/jpeg-compress.h @@ -0,0 +1,93 @@ +/* Copyright (C) 2011 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#ifndef _ANDROID_UTILS_JPEG_COMPRESS_H +#define _ANDROID_UTILS_JPEG_COMPRESS_H + +/* + * Contains declaration of utility routines that compress an RGB bitmap into + * a JPEG image. + * + * NOTE: This code uses a jpeglib library located in distrib/jpeg-6b. It's a + * 3-rd party library that uses its own type definitions that are different from + * the ones that are use elsewhere in the emulator code. For instance, in the + * emulator built for Windows, sizeof(bool) = 1, while in the jpeglib sizeof(bool) = 4. + * So, to simplify dealing with these issues, all the code that uses jpeglib should + * be compiled separately, and should include only headers that are used to compile + * jpeglib. + */ + + +/* Declares descriptor for a JPEG compression. */ +typedef struct AJPEGDesc AJPEGDesc; + +/* Creates a descriptor that will be used for compression. + * Param: + * header_size - Number of bytes to allocate for a custom header that should + * preceed the actual JPEG buffer. This is useful when sending JPEG + * somewhere else along with some extra data about the compressed image. + * cunk_size - Number of bytes to increment the compressed buffer with each time + * compressor requests more memory. + * Return: + * Initialized compression descriptor. + */ +extern AJPEGDesc* jpeg_compressor_create(int header_size, int chunk_size); + +/* Destroys compressor descriptor. + * Param: + * dsc - Compressin descriptor, obtained with jpeg_compressor_create. + */ +extern void jpeg_compressor_destroy(AJPEGDesc* dsc); + +/* Returns compressed data size. + * Param: + * dsc - Compression descriptor, obtained with jpeg_compressor_create. + * Return: + * Compressed data size. + */ +extern int jpeg_compressor_get_jpeg_size(const AJPEGDesc* dsc); + +/* Returns compressed buffer. + * Param: + * dsc - Compression descriptor, obtained with jpeg_compressor_create. + * Return: + * Compressed buffer. NOTE: if 'header_size' parameter passed to the jpeg_compressor_create + * for this descriptor was not zero, this routine returns a pointer to the custom + * header. Compressed data follows immediately after that header. + */ +extern void* jpeg_compressor_get_buffer(const AJPEGDesc* dsc); + +/* Returns size of the custom header placed before the compressed data. + * Param: + * dsc - Compression descriptor, obtained with jpeg_compressor_create. + * Return: + * Size of the custom header placed before the compressed data. + */ +extern int jpeg_compressor_get_header_size(const AJPEGDesc* dsc); + +/* Compresses a framebuffer region into JPEG image. + * Param: + * dsc - Compression descriptor, obtained with jpeg_compressor_create. + * x, y, w, h - Coordinates and sizes of framebuffer region to compress. + * bpp - Number of bytes per pixel in the framebuffer. + * bpl - Number of bytes per line in the framebuffer. + * fb - Beginning of the framebuffer. + * jpeg_quality JPEG compression quality. A number from 1 to 100. Note that + * value 10 provides pretty decent image for the purpose of multi-touch + * emulation. + */ +extern void jpeg_compressor_compress_fb(AJPEGDesc* dsc, + int x, int y, int w, int h, + int bpp, int bpl, + const uint8_t* fb, + int jpeg_quality); + +#endif /* _ANDROID_UTILS_JPEG_COMPRESS_H */ -- cgit v1.1