--- server/Makefile.am | 56 +- server/char-device.c | 1035 ++++++++++++++++++ server/char-device.h | 216 ++++ server/char_device.c | 1035 ------------------ server/char_device.h | 216 ---- server/dcc-encoders.h | 10 +- server/display-channel.h | 14 +- server/glz-encoder-dict.c | 633 +++++++++++ server/glz-encoder-dict.h | 69 ++ server/glz-encoder-priv.h | 186 ++++ server/glz-encoder.c | 311 ++++++ server/glz-encoder.h | 55 + server/glz_encoder.c | 311 ------ server/glz_encoder.h | 55 - server/glz_encoder_dictionary.c | 633 ----------- server/glz_encoder_dictionary.h | 69 -- server/glz_encoder_dictionary_protected.h | 186 ---- server/image-cache.c | 214 ++++ server/image-cache.h | 65 ++ server/inputs-channel.c | 679 ++++++++++++ server/inputs-channel.h | 38 + server/inputs_channel.c | 679 ------------ server/inputs_channel.h | 38 - server/jpeg-encoder.c | 248 +++++ server/jpeg-encoder.h | 61 ++ server/jpeg_encoder.c | 248 ----- server/jpeg_encoder.h | 61 -- server/main-channel.c | 1345 ++++++++++++++++++++++++ server/main-channel.h | 103 ++ server/main-dispatcher.c | 217 ++++ server/main-dispatcher.h | 36 + server/main_channel.c | 1345 ------------------------ server/main_channel.h | 103 -- server/main_dispatcher.c | 217 ---- server/main_dispatcher.h | 36 - server/memslot.c | 184 ++++ server/memslot.h | 72 ++ server/migration-protocol.h | 213 ++++ server/migration_protocol.h | 213 ---- server/mjpeg-encoder.c | 1375 ++++++++++++++++++++++++ server/mjpeg-encoder.h | 100 ++ server/mjpeg_encoder.c | 1375 ------------------------ server/mjpeg_encoder.h | 100 -- server/red_channel.c | 2 +- server/red_dispatcher.c | 2 +- server/red_memslots.c | 184 ---- server/red_memslots.h | 72 -- server/red_parse_qxl.c | 2 +- server/red_parse_qxl.h | 2 +- server/red_record_qxl.c | 4 +- server/red_record_qxl.h | 2 +- server/red_replay_qxl.c | 2 +- server/reds.c | 12 +- server/reds.h | 4 +- server/reds_stream.c | 2 +- server/reds_sw_canvas.c | 26 - server/reds_sw_canvas.h | 24 - server/smartcard.c | 4 +- server/snd_worker.c | 1625 ----------------------------- server/snd_worker.h | 33 - server/sound.c | 1625 +++++++++++++++++++++++++++++ server/sound.h | 33 + server/spice_image_cache.c | 214 ---- server/spice_image_cache.h | 65 -- server/spicevmc.c | 4 +- server/stream.h | 4 +- server/sw-canvas.c | 26 + server/sw-canvas.h | 24 + server/zlib-encoder.c | 125 +++ server/zlib-encoder.h | 47 + server/zlib_encoder.c | 125 --- server/zlib_encoder.h | 47 - 72 files changed, 9398 insertions(+), 9398 deletions(-) create mode 100644 server/char-device.c create mode 100644 server/char-device.h delete mode 100644 server/char_device.c delete mode 100644 server/char_device.h create mode 100644 server/glz-encoder-dict.c create mode 100644 server/glz-encoder-dict.h create mode 100644 server/glz-encoder-priv.h create mode 100644 server/glz-encoder.c create mode 100644 server/glz-encoder.h delete mode 100644 server/glz_encoder.c delete mode 100644 server/glz_encoder.h delete mode 100644 server/glz_encoder_dictionary.c delete mode 100644 server/glz_encoder_dictionary.h delete mode 100644 server/glz_encoder_dictionary_protected.h create mode 100644 server/image-cache.c create mode 100644 server/image-cache.h create mode 100644 server/inputs-channel.c create mode 100644 server/inputs-channel.h delete mode 100644 server/inputs_channel.c delete mode 100644 server/inputs_channel.h create mode 100644 server/jpeg-encoder.c create mode 100644 server/jpeg-encoder.h delete mode 100644 server/jpeg_encoder.c delete mode 100644 server/jpeg_encoder.h create mode 100644 server/main-channel.c create mode 100644 server/main-channel.h create mode 100644 server/main-dispatcher.c create mode 100644 server/main-dispatcher.h delete mode 100644 server/main_channel.c delete mode 100644 server/main_channel.h delete mode 100644 server/main_dispatcher.c delete mode 100644 server/main_dispatcher.h create mode 100644 server/memslot.c create mode 100644 server/memslot.h create mode 100644 server/migration-protocol.h delete mode 100644 server/migration_protocol.h create mode 100644 server/mjpeg-encoder.c create mode 100644 server/mjpeg-encoder.h delete mode 100644 server/mjpeg_encoder.c delete mode 100644 server/mjpeg_encoder.h delete mode 100644 server/red_memslots.c delete mode 100644 server/red_memslots.h delete mode 100644 server/reds_sw_canvas.c delete mode 100644 server/reds_sw_canvas.h delete mode 100644 server/snd_worker.c delete mode 100644 server/snd_worker.h create mode 100644 server/sound.c create mode 100644 server/sound.h delete mode 100644 server/spice_image_cache.c delete mode 100644 server/spice_image_cache.h create mode 100644 server/sw-canvas.c create mode 100644 server/sw-canvas.h create mode 100644 server/zlib-encoder.c create mode 100644 server/zlib-encoder.h delete mode 100644 server/zlib_encoder.c delete mode 100644 server/zlib_encoder.h diff --git a/server/Makefile.am b/server/Makefile.am index 6b45a42..e964fb1 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -66,25 +66,25 @@ libspice_server_la_SOURCES = \ agent-msg-filter.c \ agent-msg-filter.h \ cache-item.h \ - char_device.c \ - char_device.h \ + char-device.c \ + char-device.h \ demarshallers.h \ - glz_encoder.c \ - glz_encoder.h \ + glz-encoder.c \ + glz-encoder.h \ glz_encoder_config.h \ - glz_encoder_dictionary.c \ - glz_encoder_dictionary.h \ - glz_encoder_dictionary_protected.h \ - inputs_channel.c \ - inputs_channel.h \ - jpeg_encoder.c \ - jpeg_encoder.h \ + glz-encoder-dict.c \ + glz-encoder-dict.h \ + glz-encoder-priv.h \ + inputs-channel.c \ + inputs-channel.h \ + jpeg-encoder.c \ + jpeg-encoder.h \ lz4_encoder.c \ lz4_encoder.h \ - main_channel.c \ - main_channel.h \ - mjpeg_encoder.c \ - mjpeg_encoder.h \ + main-channel.c \ + main-channel.h \ + mjpeg-encoder.c \ + mjpeg-encoder.h \ red_channel.c \ red_channel.h \ red_common.h \ @@ -92,11 +92,11 @@ libspice_server_la_SOURCES = \ dispatcher.h \ red_dispatcher.c \ red_dispatcher.h \ - main_dispatcher.c \ - main_dispatcher.h \ - migration_protocol.h \ - red_memslots.c \ - red_memslots.h \ + main-dispatcher.c \ + main-dispatcher.h \ + migration-protocol.h \ + memslot.c \ + memslot.h \ red_parse_qxl.c \ red_record_qxl.c \ red_record_qxl.h \ @@ -114,20 +114,20 @@ libspice_server_la_SOURCES = \ reds-private.h \ reds_stream.c \ reds_stream.h \ - reds_sw_canvas.c \ - reds_sw_canvas.h \ - snd_worker.c \ - snd_worker.h \ + sw-canvas.c \ + sw-canvas.h \ + sound.c \ + sound.h \ stat.h \ spicevmc.c \ spice_timer_queue.c \ spice_timer_queue.h \ - zlib_encoder.c \ - zlib_encoder.h \ + zlib-encoder.c \ + zlib-encoder.h \ spice_bitmap_utils.h \ spice_bitmap_utils.c \ - spice_image_cache.h \ - spice_image_cache.c \ + image-cache.h \ + image-cache.c \ pixmap-cache.h \ pixmap-cache.c \ tree.h \ diff --git a/server/char-device.c b/server/char-device.c new file mode 100644 index 0000000..3790fab --- /dev/null +++ b/server/char-device.c @@ -0,0 +1,1035 @@ +/* spice-server char device flow control code + + Copyright (C) 2012 Red Hat, Inc. + + Red Hat Authors: + Yonit Halperin <yhalperi@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http:www.gnu.org/licenses/>. +*/ + + +#include <config.h> +#include "char-device.h" +#include "red_channel.h" +#include "reds.h" + +#define CHAR_DEVICE_WRITE_TO_TIMEOUT 100 +#define SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT 30000 +#define MAX_POOL_SIZE (10 * 64 * 1024) + +typedef struct SpiceCharDeviceClientState SpiceCharDeviceClientState; +struct SpiceCharDeviceClientState { + RingItem link; + SpiceCharDeviceState *dev; + RedClient *client; + int do_flow_control; + uint64_t num_client_tokens; + uint64_t num_client_tokens_free; /* client messages that were consumed by the device */ + uint64_t num_send_tokens; /* send to client */ + SpiceTimer *wait_for_tokens_timer; + int wait_for_tokens_started; + Ring send_queue; + uint32_t send_queue_size; + uint32_t max_send_queue_size; +}; + +struct SpiceCharDeviceState { + int running; + int active; /* has read/write been performed since the device was started */ + int wait_for_migrate_data; + uint32_t refs; + + Ring write_queue; + Ring write_bufs_pool; + uint64_t cur_pool_size; + SpiceCharDeviceWriteBuffer *cur_write_buf; + uint8_t *cur_write_buf_pos; + SpiceTimer *write_to_dev_timer; + uint64_t num_self_tokens; + + Ring clients; /* list of SpiceCharDeviceClientState */ + uint32_t num_clients; + + uint64_t client_tokens_interval; /* frequency of returning tokens to the client */ + SpiceCharDeviceInstance *sin; + + int during_read_from_device; + int during_write_to_device; + + SpiceCharDeviceCallbacks cbs; + void *opaque; +}; + +enum { + WRITE_BUFFER_ORIGIN_NONE, + WRITE_BUFFER_ORIGIN_CLIENT, + WRITE_BUFFER_ORIGIN_SERVER, + WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN, +}; + +/* Holding references for avoiding access violation if the char device was + * destroyed during a callback */ +static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev); +static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev); +static void spice_char_device_write_buffer_unref(SpiceCharDeviceWriteBuffer *write_buf); + +static void spice_char_dev_write_retry(void *opaque); + +typedef struct SpiceCharDeviceMsgToClientItem { + RingItem link; + SpiceCharDeviceMsgToClient *msg; +} SpiceCharDeviceMsgToClientItem; + +static void spice_char_device_write_buffer_free(SpiceCharDeviceWriteBuffer *buf) +{ + if (buf == NULL) + return; + + free(buf->buf); + free(buf); +} + +static void write_buffers_queue_free(Ring *write_queue) +{ + while (!ring_is_empty(write_queue)) { + RingItem *item = ring_get_tail(write_queue); + SpiceCharDeviceWriteBuffer *buf; + + ring_remove(item); + buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + spice_char_device_write_buffer_free(buf); + } +} + +static void spice_char_device_write_buffer_pool_add(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *buf) +{ + if (buf->refs == 1 && + dev->cur_pool_size < MAX_POOL_SIZE) { + buf->buf_used = 0; + buf->origin = WRITE_BUFFER_ORIGIN_NONE; + buf->client = NULL; + dev->cur_pool_size += buf->buf_size; + ring_add(&dev->write_bufs_pool, &buf->link); + return; + } + + /* Buffer still being used - just unref for the caller */ + spice_char_device_write_buffer_unref(buf); +} + +static void spice_char_device_client_send_queue_free(SpiceCharDeviceState *dev, + SpiceCharDeviceClientState *dev_client) +{ + spice_debug("send_queue_empty %d", ring_is_empty(&dev_client->send_queue)); + while (!ring_is_empty(&dev_client->send_queue)) { + RingItem *item = ring_get_tail(&dev_client->send_queue); + SpiceCharDeviceMsgToClientItem *msg_item = SPICE_CONTAINEROF(item, + SpiceCharDeviceMsgToClientItem, + link); + + ring_remove(item); + dev->cbs.unref_msg_to_client(msg_item->msg, dev->opaque); + free(msg_item); + } + dev_client->num_send_tokens += dev_client->send_queue_size; + dev_client->send_queue_size = 0; +} + +static void spice_char_device_client_free(SpiceCharDeviceState *dev, + SpiceCharDeviceClientState *dev_client) +{ + RingItem *item, *next; + + if (dev_client->wait_for_tokens_timer) { + core->timer_remove(dev_client->wait_for_tokens_timer); + } + + spice_char_device_client_send_queue_free(dev, dev_client); + + /* remove write buffers that are associated with the client */ + spice_debug("write_queue_is_empty %d", ring_is_empty(&dev->write_queue) && !dev->cur_write_buf); + RING_FOREACH_SAFE(item, next, &dev->write_queue) { + SpiceCharDeviceWriteBuffer *write_buf; + + write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && + write_buf->client == dev_client->client) { + ring_remove(item); + spice_char_device_write_buffer_pool_add(dev, write_buf); + } + } + + if (dev->cur_write_buf && dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && + dev->cur_write_buf->client == dev_client->client) { + dev->cur_write_buf->origin = WRITE_BUFFER_ORIGIN_NONE; + dev->cur_write_buf->client = NULL; + } + + dev->num_clients--; + ring_remove(&dev_client->link); + free(dev_client); +} + +static void spice_char_device_handle_client_overflow(SpiceCharDeviceClientState *dev_client) +{ + SpiceCharDeviceState *dev = dev_client->dev; + spice_printerr("dev %p client %p ", dev, dev_client); + dev->cbs.remove_client(dev_client->client, dev->opaque); +} + +static SpiceCharDeviceClientState *spice_char_device_client_find(SpiceCharDeviceState *dev, + RedClient *client) +{ + RingItem *item; + + RING_FOREACH(item, &dev->clients) { + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); + if (dev_client->client == client) { + return dev_client; + } + } + return NULL; +} + +/*************************** + * Reading from the device * + **************************/ + +static void device_client_wait_for_tokens_timeout(void *opaque) +{ + SpiceCharDeviceClientState *dev_client = opaque; + + spice_char_device_handle_client_overflow(dev_client); +} + +static int spice_char_device_can_send_to_client(SpiceCharDeviceClientState *dev_client) +{ + return !dev_client->do_flow_control || dev_client->num_send_tokens; +} + +static uint64_t spice_char_device_max_send_tokens(SpiceCharDeviceState *dev) +{ + RingItem *item; + uint64_t max = 0; + + RING_FOREACH(item, &dev->clients) { + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); + + if (!dev_client->do_flow_control) { + max = ~0; + break; + } + + if (dev_client->num_send_tokens > max) { + max = dev_client->num_send_tokens; + } + } + return max; +} + +static void spice_char_device_add_msg_to_client_queue(SpiceCharDeviceClientState *dev_client, + SpiceCharDeviceMsgToClient *msg) +{ + SpiceCharDeviceState *dev = dev_client->dev; + SpiceCharDeviceMsgToClientItem *msg_item; + + if (dev_client->send_queue_size >= dev_client->max_send_queue_size) { + spice_char_device_handle_client_overflow(dev_client); + return; + } + + msg_item = spice_new0(SpiceCharDeviceMsgToClientItem, 1); + msg_item->msg = dev->cbs.ref_msg_to_client(msg, dev->opaque); + ring_add(&dev_client->send_queue, &msg_item->link); + dev_client->send_queue_size++; + if (!dev_client->wait_for_tokens_started) { + core->timer_start(dev_client->wait_for_tokens_timer, + SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT); + dev_client->wait_for_tokens_started = TRUE; + } +} + +static void spice_char_device_send_msg_to_clients(SpiceCharDeviceState *dev, + SpiceCharDeviceMsgToClient *msg) +{ + RingItem *item, *next; + + RING_FOREACH_SAFE(item, next, &dev->clients) { + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); + if (spice_char_device_can_send_to_client(dev_client)) { + dev_client->num_send_tokens--; + spice_assert(ring_is_empty(&dev_client->send_queue)); + dev->cbs.send_msg_to_client(msg, dev_client->client, dev->opaque); + + /* don't refer to dev_client anymore, it may have been released */ + } else { + spice_char_device_add_msg_to_client_queue(dev_client, msg); + } + } +} + +static int spice_char_device_read_from_device(SpiceCharDeviceState *dev) +{ + uint64_t max_send_tokens; + int did_read = FALSE; + + if (!dev->running || dev->wait_for_migrate_data || !dev->sin) { + return FALSE; + } + + /* There are 2 scenarios where we can get called recursively: + * 1) spice-vmc vmc_read triggering flush of throttled data, recalling wakeup + * (virtio) + * 2) in case of sending messages to the client, and unreferencing the + * msg, we trigger another read. + */ + if (dev->during_read_from_device++ > 0) { + return FALSE; + } + + max_send_tokens = spice_char_device_max_send_tokens(dev); + spice_char_device_state_ref(dev); + /* + * Reading from the device only in case at least one of the clients have a free token. + * All messages will be discarded if no client is attached to the device + */ + while ((max_send_tokens || ring_is_empty(&dev->clients)) && dev->running) { + SpiceCharDeviceMsgToClient *msg; + + msg = dev->cbs.read_one_msg_from_device(dev->sin, dev->opaque); + if (!msg) { + if (dev->during_read_from_device > 1) { + dev->during_read_from_device = 1; + continue; /* a wakeup might have been called during the read - + make sure it doesn't get lost */ + } + break; + } + did_read = TRUE; + spice_char_device_send_msg_to_clients(dev, msg); + dev->cbs.unref_msg_to_client(msg, dev->opaque); + max_send_tokens--; + } + dev->during_read_from_device = 0; + if (dev->running) { + dev->active = dev->active || did_read; + } + spice_char_device_state_unref(dev); + return did_read; +} + +static void spice_char_device_client_send_queue_push(SpiceCharDeviceClientState *dev_client) +{ + RingItem *item; + while ((item = ring_get_tail(&dev_client->send_queue)) && + spice_char_device_can_send_to_client(dev_client)) { + SpiceCharDeviceMsgToClientItem *msg_item; + + msg_item = SPICE_CONTAINEROF(item, SpiceCharDeviceMsgToClientItem, link); + ring_remove(item); + + dev_client->num_send_tokens--; + dev_client->dev->cbs.send_msg_to_client(msg_item->msg, + dev_client->client, + dev_client->dev->opaque); + dev_client->dev->cbs.unref_msg_to_client(msg_item->msg, dev_client->dev->opaque); + dev_client->send_queue_size--; + free(msg_item); + } +} + +static void spice_char_device_send_to_client_tokens_absorb(SpiceCharDeviceClientState *dev_client, + uint32_t tokens) +{ + dev_client->num_send_tokens += tokens; + + if (dev_client->send_queue_size) { + spice_assert(dev_client->num_send_tokens == tokens); + spice_char_device_client_send_queue_push(dev_client); + } + + if (spice_char_device_can_send_to_client(dev_client)) { + core->timer_cancel(dev_client->wait_for_tokens_timer); + dev_client->wait_for_tokens_started = FALSE; + spice_char_device_read_from_device(dev_client->dev); + } else if (dev_client->send_queue_size) { + core->timer_start(dev_client->wait_for_tokens_timer, + SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT); + dev_client->wait_for_tokens_started = TRUE; + } +} + +void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev, + RedClient *client, + uint32_t tokens) +{ + SpiceCharDeviceClientState *dev_client; + + dev_client = spice_char_device_client_find(dev, client); + + if (!dev_client) { + spice_error("client wasn't found dev %p client %p", dev, client); + return; + } + spice_char_device_send_to_client_tokens_absorb(dev_client, tokens); +} + +void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev, + RedClient *client, + uint32_t tokens) +{ + SpiceCharDeviceClientState *dev_client; + + dev_client = spice_char_device_client_find(dev, client); + + if (!dev_client) { + spice_error("client wasn't found dev %p client %p", dev, client); + return; + } + + dev_client->num_send_tokens = 0; + spice_char_device_send_to_client_tokens_absorb(dev_client, tokens); +} + +/************************** + * Writing to the device * +***************************/ + +static void spice_char_device_client_tokens_add(SpiceCharDeviceState *dev, + SpiceCharDeviceClientState *dev_client, + uint32_t num_tokens) +{ + if (!dev_client->do_flow_control) { + return; + } + if (num_tokens > 1) { + spice_debug("#tokens > 1 (=%u)", num_tokens); + } + dev_client->num_client_tokens_free += num_tokens; + if (dev_client->num_client_tokens_free >= dev->client_tokens_interval) { + uint32_t tokens = dev_client->num_client_tokens_free; + + dev_client->num_client_tokens += dev_client->num_client_tokens_free; + dev_client->num_client_tokens_free = 0; + dev->cbs.send_tokens_to_client(dev_client->client, + tokens, + dev->opaque); + } +} + +static int spice_char_device_write_to_device(SpiceCharDeviceState *dev) +{ + SpiceCharDeviceInterface *sif; + int total = 0; + int n; + + if (!dev->running || dev->wait_for_migrate_data || !dev->sin) { + return 0; + } + + /* protect against recursion with spice_char_device_wakeup */ + if (dev->during_write_to_device++ > 0) { + return 0; + } + + spice_char_device_state_ref(dev); + + if (dev->write_to_dev_timer) { + core->timer_cancel(dev->write_to_dev_timer); + } + + sif = SPICE_CONTAINEROF(dev->sin->base.sif, SpiceCharDeviceInterface, base); + while (dev->running) { + uint32_t write_len; + + if (!dev->cur_write_buf) { + RingItem *item = ring_get_tail(&dev->write_queue); + if (!item) { + break; + } + dev->cur_write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + dev->cur_write_buf_pos = dev->cur_write_buf->buf; + ring_remove(item); + } + + write_len = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used - + dev->cur_write_buf_pos; + n = sif->write(dev->sin, dev->cur_write_buf_pos, write_len); + if (n <= 0) { + if (dev->during_write_to_device > 1) { + dev->during_write_to_device = 1; + continue; /* a wakeup might have been called during the write - + make sure it doesn't get lost */ + } + break; + } + total += n; + write_len -= n; + if (!write_len) { + SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf; + dev->cur_write_buf = NULL; + spice_char_device_write_buffer_release(dev, release_buf); + continue; + } + dev->cur_write_buf_pos += n; + } + /* retry writing as long as the write queue is not empty */ + if (dev->running) { + if (dev->cur_write_buf) { + if (dev->write_to_dev_timer) { + core->timer_start(dev->write_to_dev_timer, + CHAR_DEVICE_WRITE_TO_TIMEOUT); + } + } else { + spice_assert(ring_is_empty(&dev->write_queue)); + } + dev->active = dev->active || total; + } + dev->during_write_to_device = 0; + spice_char_device_state_unref(dev); + return total; +} + +static void spice_char_dev_write_retry(void *opaque) +{ + SpiceCharDeviceState *dev = opaque; + + if (dev->write_to_dev_timer) { + core->timer_cancel(dev->write_to_dev_timer); + } + spice_char_device_write_to_device(dev); +} + +static SpiceCharDeviceWriteBuffer *__spice_char_device_write_buffer_get( + SpiceCharDeviceState *dev, RedClient *client, + int size, int origin, int migrated_data_tokens) +{ + RingItem *item; + SpiceCharDeviceWriteBuffer *ret; + + if (origin == WRITE_BUFFER_ORIGIN_SERVER && !dev->num_self_tokens) { + return NULL; + } + + if ((item = ring_get_tail(&dev->write_bufs_pool))) { + ret = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + ring_remove(item); + dev->cur_pool_size -= ret->buf_size; + } else { + ret = spice_new0(SpiceCharDeviceWriteBuffer, 1); + } + + spice_assert(!ret->buf_used); + + if (ret->buf_size < size) { + ret->buf = spice_realloc(ret->buf, size); + ret->buf_size = size; + } + ret->origin = origin; + + if (origin == WRITE_BUFFER_ORIGIN_CLIENT) { + spice_assert(client); + SpiceCharDeviceClientState *dev_client = spice_char_device_client_find(dev, client); + if (dev_client) { + if (!migrated_data_tokens && + dev_client->do_flow_control && !dev_client->num_client_tokens) { + spice_printerr("token violation: dev %p client %p", dev, client); + spice_char_device_handle_client_overflow(dev_client); + goto error; + } + ret->client = client; + if (!migrated_data_tokens && dev_client->do_flow_control) { + dev_client->num_client_tokens--; + } + } else { + /* it is possible that the client was removed due to send tokens underflow, but + * the caller still receive messages from the client */ + spice_printerr("client not found: dev %p client %p", dev, client); + goto error; + } + } else if (origin == WRITE_BUFFER_ORIGIN_SERVER) { + dev->num_self_tokens--; + } + + ret->token_price = migrated_data_tokens ? migrated_data_tokens : 1; + ret->refs = 1; + return ret; +error: + dev->cur_pool_size += ret->buf_size; + ring_add(&dev->write_bufs_pool, &ret->link); + return NULL; +} + +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev, + RedClient *client, + int size) +{ + return __spice_char_device_write_buffer_get(dev, client, size, + client ? WRITE_BUFFER_ORIGIN_CLIENT : WRITE_BUFFER_ORIGIN_SERVER, + 0); +} + +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get_server_no_token( + SpiceCharDeviceState *dev, int size) +{ + return __spice_char_device_write_buffer_get(dev, NULL, size, + WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN, 0); +} + +static SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_ref(SpiceCharDeviceWriteBuffer *write_buf) +{ + spice_assert(write_buf); + + write_buf->refs++; + return write_buf; +} + +static void spice_char_device_write_buffer_unref(SpiceCharDeviceWriteBuffer *write_buf) +{ + spice_assert(write_buf); + + write_buf->refs--; + if (write_buf->refs == 0) + spice_char_device_write_buffer_free(write_buf); +} + +void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *write_buf) +{ + spice_assert(dev); + /* caller shouldn't add buffers for client that was removed */ + if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && + !spice_char_device_client_find(dev, write_buf->client)) { + spice_printerr("client not found: dev %p client %p", dev, write_buf->client); + spice_char_device_write_buffer_pool_add(dev, write_buf); + return; + } + + ring_add(&dev->write_queue, &write_buf->link); + spice_char_device_write_to_device(dev); +} + +void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *write_buf) +{ + int buf_origin = write_buf->origin; + uint32_t buf_token_price = write_buf->token_price; + RedClient *client = write_buf->client; + + spice_assert(!ring_item_is_linked(&write_buf->link)); + if (!dev) { + spice_printerr("no device. write buffer is freed"); + spice_char_device_write_buffer_free(write_buf); + return; + } + + spice_assert(dev->cur_write_buf != write_buf); + + spice_char_device_write_buffer_pool_add(dev, write_buf); + if (buf_origin == WRITE_BUFFER_ORIGIN_CLIENT) { + SpiceCharDeviceClientState *dev_client; + + spice_assert(client); + dev_client = spice_char_device_client_find(dev, client); + /* when a client is removed, we remove all the buffers that are associated with it */ + spice_assert(dev_client); + spice_char_device_client_tokens_add(dev, dev_client, buf_token_price); + } else if (buf_origin == WRITE_BUFFER_ORIGIN_SERVER) { + dev->num_self_tokens++; + if (dev->cbs.on_free_self_token) { + dev->cbs.on_free_self_token(dev->opaque); + } + } +} + +/******************************** + * char_device_state management * + ********************************/ + +SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin, + uint32_t client_tokens_interval, + uint32_t self_tokens, + SpiceCharDeviceCallbacks *cbs, + void *opaque) +{ + SpiceCharDeviceState *char_dev; + SpiceCharDeviceInterface *sif; + + spice_assert(sin); + spice_assert(cbs->read_one_msg_from_device && cbs->ref_msg_to_client && + cbs->unref_msg_to_client && cbs->send_msg_to_client && + cbs->send_tokens_to_client && cbs->remove_client); + + char_dev = spice_new0(SpiceCharDeviceState, 1); + char_dev->sin = sin; + char_dev->cbs = *cbs; + char_dev->opaque = opaque; + char_dev->client_tokens_interval = client_tokens_interval; + char_dev->num_self_tokens = self_tokens; + + ring_init(&char_dev->write_queue); + ring_init(&char_dev->write_bufs_pool); + ring_init(&char_dev->clients); + + sif = SPICE_CONTAINEROF(char_dev->sin->base.sif, SpiceCharDeviceInterface, base); + if (sif->base.minor_version <= 2 || + !(sif->flags & SPICE_CHAR_DEVICE_NOTIFY_WRITABLE)) { + char_dev->write_to_dev_timer = core->timer_add(spice_char_dev_write_retry, char_dev); + if (!char_dev->write_to_dev_timer) { + spice_error("failed creating char dev write timer"); + } + } + + char_dev->refs = 1; + sin->st = char_dev; + spice_debug("sin %p dev_state %p", sin, char_dev); + return char_dev; +} + +void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *state, + SpiceCharDeviceInstance *sin) +{ + spice_debug("sin %p dev_state %p", sin, state); + state->sin = sin; + sin->st = state; +} + +void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev) +{ + return dev->opaque; +} + +static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev) +{ + char_dev->refs++; +} + +static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev) +{ + /* The refs field protects the char_dev from being deallocated in + * case spice_char_device_state_destroy has been called + * during a callabck, and we might still access the char_dev afterwards. + * spice_char_device_state_unref is always coupled with a preceding + * spice_char_device_state_ref. Here, refs can turn 0 + * only when spice_char_device_state_destroy is called in between + * the calls to spice_char_device_state_ref and spice_char_device_state_unref.*/ + if (!--char_dev->refs) { + free(char_dev); + } +} + +void spice_char_device_state_destroy(SpiceCharDeviceState *char_dev) +{ + reds_on_char_device_state_destroy(char_dev); + if (char_dev->write_to_dev_timer) { + core->timer_remove(char_dev->write_to_dev_timer); + char_dev->write_to_dev_timer = NULL; + } + write_buffers_queue_free(&char_dev->write_queue); + write_buffers_queue_free(&char_dev->write_bufs_pool); + char_dev->cur_pool_size = 0; + spice_char_device_write_buffer_free(char_dev->cur_write_buf); + char_dev->cur_write_buf = NULL; + + while (!ring_is_empty(&char_dev->clients)) { + RingItem *item = ring_get_tail(&char_dev->clients); + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); + spice_char_device_client_free(char_dev, dev_client); + } + char_dev->running = FALSE; + + spice_char_device_state_unref(char_dev); +} + +int spice_char_device_client_add(SpiceCharDeviceState *dev, + RedClient *client, + int do_flow_control, + uint32_t max_send_queue_size, + uint32_t num_client_tokens, + uint32_t num_send_tokens, + int wait_for_migrate_data) +{ + SpiceCharDeviceClientState *dev_client; + + spice_assert(dev); + spice_assert(client); + + if (wait_for_migrate_data && (dev->num_clients > 0 || dev->active)) { + spice_warning("can't restore device %p from migration data. The device " + "has already been active", dev); + return FALSE; + } + + dev->wait_for_migrate_data = wait_for_migrate_data; + + spice_debug("dev_state %p client %p", dev, client); + dev_client = spice_new0(SpiceCharDeviceClientState, 1); + dev_client->dev = dev; + dev_client->client = client; + ring_init(&dev_client->send_queue); + dev_client->send_queue_size = 0; + dev_client->max_send_queue_size = max_send_queue_size; + dev_client->do_flow_control = do_flow_control; + if (do_flow_control) { + dev_client->wait_for_tokens_timer = core->timer_add(device_client_wait_for_tokens_timeout, + dev_client); + if (!dev_client->wait_for_tokens_timer) { + spice_error("failed to create wait for tokens timer"); + } + dev_client->num_client_tokens = num_client_tokens; + dev_client->num_send_tokens = num_send_tokens; + } else { + dev_client->num_client_tokens = ~0; + dev_client->num_send_tokens = ~0; + } + ring_add(&dev->clients, &dev_client->link); + dev->num_clients++; + /* Now that we have a client, forward any pending device data */ + spice_char_device_wakeup(dev); + return TRUE; +} + +void spice_char_device_client_remove(SpiceCharDeviceState *dev, + RedClient *client) +{ + SpiceCharDeviceClientState *dev_client; + + spice_debug("dev_state %p client %p", dev, client); + dev_client = spice_char_device_client_find(dev, client); + + if (!dev_client) { + spice_error("client wasn't found"); + return; + } + spice_char_device_client_free(dev, dev_client); + if (dev->wait_for_migrate_data) { + spice_assert(dev->num_clients == 0); + dev->wait_for_migrate_data = FALSE; + spice_char_device_read_from_device(dev); + } + + if (dev->num_clients == 0) { + spice_debug("client removed, memory pool will be freed (%lu bytes)", dev->cur_pool_size); + write_buffers_queue_free(&dev->write_bufs_pool); + dev->cur_pool_size = 0; + } +} + +int spice_char_device_client_exists(SpiceCharDeviceState *dev, + RedClient *client) +{ + return (spice_char_device_client_find(dev, client) != NULL); +} + +void spice_char_device_start(SpiceCharDeviceState *dev) +{ + spice_debug("dev_state %p", dev); + dev->running = TRUE; + spice_char_device_state_ref(dev); + while (spice_char_device_write_to_device(dev) || + spice_char_device_read_from_device(dev)); + spice_char_device_state_unref(dev); +} + +void spice_char_device_stop(SpiceCharDeviceState *dev) +{ + spice_debug("dev_state %p", dev); + dev->running = FALSE; + dev->active = FALSE; + if (dev->write_to_dev_timer) { + core->timer_cancel(dev->write_to_dev_timer); + } +} + +void spice_char_device_reset(SpiceCharDeviceState *dev) +{ + RingItem *client_item; + + spice_char_device_stop(dev); + dev->wait_for_migrate_data = FALSE; + spice_debug("dev_state %p", dev); + while (!ring_is_empty(&dev->write_queue)) { + RingItem *item = ring_get_tail(&dev->write_queue); + SpiceCharDeviceWriteBuffer *buf; + + ring_remove(item); + buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + /* tracking the tokens */ + spice_char_device_write_buffer_release(dev, buf); + } + if (dev->cur_write_buf) { + SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf; + + dev->cur_write_buf = NULL; + spice_char_device_write_buffer_release(dev, release_buf); + } + + RING_FOREACH(client_item, &dev->clients) { + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(client_item, SpiceCharDeviceClientState, link); + spice_char_device_client_send_queue_free(dev, dev_client); + } + dev->sin = NULL; +} + +void spice_char_device_wakeup(SpiceCharDeviceState *dev) +{ + spice_char_device_write_to_device(dev); + spice_char_device_read_from_device(dev); +} + +/************* + * Migration * + * **********/ + +void spice_char_device_state_migrate_data_marshall_empty(SpiceMarshaller *m) +{ + SpiceMigrateDataCharDevice *mig_data; + + spice_debug(NULL); + mig_data = (SpiceMigrateDataCharDevice *)spice_marshaller_reserve_space(m, + sizeof(*mig_data)); + memset(mig_data, 0, sizeof(*mig_data)); + mig_data->version = SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION; + mig_data->connected = FALSE; +} + +static void migrate_data_marshaller_write_buffer_free(uint8_t *data, void *opaque) +{ + SpiceCharDeviceWriteBuffer *write_buf = (SpiceCharDeviceWriteBuffer *)opaque; + + spice_char_device_write_buffer_unref(write_buf); +} + +void spice_char_device_state_migrate_data_marshall(SpiceCharDeviceState *dev, + SpiceMarshaller *m) +{ + SpiceCharDeviceClientState *client_state; + RingItem *item; + uint32_t *write_to_dev_size_ptr; + uint32_t *write_to_dev_tokens_ptr; + SpiceMarshaller *m2; + + /* multi-clients are not supported */ + spice_assert(dev->num_clients == 1); + client_state = SPICE_CONTAINEROF(ring_get_tail(&dev->clients), + SpiceCharDeviceClientState, + link); + /* FIXME: if there were more than one client before the marshalling, + * it is possible that the send_queue_size > 0, and the send data + * should be migrated as well */ + spice_assert(client_state->send_queue_size == 0); + spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION); + spice_marshaller_add_uint8(m, 1); /* connected */ + spice_marshaller_add_uint32(m, client_state->num_client_tokens); + spice_marshaller_add_uint32(m, client_state->num_send_tokens); + write_to_dev_size_ptr = (uint32_t *)spice_marshaller_reserve_space(m, sizeof(uint32_t)); + write_to_dev_tokens_ptr = (uint32_t *)spice_marshaller_reserve_space(m, sizeof(uint32_t)); + *write_to_dev_size_ptr = 0; + *write_to_dev_tokens_ptr = 0; + + m2 = spice_marshaller_get_ptr_submarshaller(m, 0); + if (dev->cur_write_buf) { + uint32_t buf_remaining = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used - + dev->cur_write_buf_pos; + spice_marshaller_add_ref_full(m2, dev->cur_write_buf_pos, buf_remaining, + migrate_data_marshaller_write_buffer_free, + spice_char_device_write_buffer_ref(dev->cur_write_buf) + ); + *write_to_dev_size_ptr += buf_remaining; + if (dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT) { + spice_assert(dev->cur_write_buf->client == client_state->client); + (*write_to_dev_tokens_ptr) += dev->cur_write_buf->token_price; + } + } + + RING_FOREACH_REVERSED(item, &dev->write_queue) { + SpiceCharDeviceWriteBuffer *write_buf; + + write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + spice_marshaller_add_ref_full(m2, write_buf->buf, write_buf->buf_used, + migrate_data_marshaller_write_buffer_free, + spice_char_device_write_buffer_ref(write_buf) + ); + *write_to_dev_size_ptr += write_buf->buf_used; + if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT) { + spice_assert(write_buf->client == client_state->client); + (*write_to_dev_tokens_ptr) += write_buf->token_price; + } + } + spice_debug("migration data dev %p: write_queue size %u tokens %u", + dev, *write_to_dev_size_ptr, *write_to_dev_tokens_ptr); +} + +int spice_char_device_state_restore(SpiceCharDeviceState *dev, + SpiceMigrateDataCharDevice *mig_data) +{ + SpiceCharDeviceClientState *client_state; + uint32_t client_tokens_window; + + spice_assert(dev->num_clients == 1 && dev->wait_for_migrate_data); + + client_state = SPICE_CONTAINEROF(ring_get_tail(&dev->clients), + SpiceCharDeviceClientState, + link); + if (mig_data->version > SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION) { + spice_error("dev %p error: migration data version %u is bigger than self %u", + dev, mig_data->version, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION); + return FALSE; + } + spice_assert(!dev->cur_write_buf && ring_is_empty(&dev->write_queue)); + spice_assert(mig_data->connected); + + client_tokens_window = client_state->num_client_tokens; /* initial state of tokens */ + client_state->num_client_tokens = mig_data->num_client_tokens; + /* assumption: client_tokens_window stays the same across severs */ + client_state->num_client_tokens_free = client_tokens_window - + mig_data->num_client_tokens - + mig_data->write_num_client_tokens; + client_state->num_send_tokens = mig_data->num_send_tokens; + + if (mig_data->write_size > 0) { + if (mig_data->write_num_client_tokens) { + dev->cur_write_buf = + __spice_char_device_write_buffer_get(dev, client_state->client, + mig_data->write_size, WRITE_BUFFER_ORIGIN_CLIENT, + mig_data->write_num_client_tokens); + } else { + dev->cur_write_buf = + __spice_char_device_write_buffer_get(dev, NULL, + mig_data->write_size, WRITE_BUFFER_ORIGIN_SERVER, 0); + } + /* the first write buffer contains all the data that was saved for migration */ + memcpy(dev->cur_write_buf->buf, + ((uint8_t *)mig_data) + mig_data->write_data_ptr - sizeof(SpiceMigrateDataHeader), + mig_data->write_size); + dev->cur_write_buf->buf_used = mig_data->write_size; + dev->cur_write_buf_pos = dev->cur_write_buf->buf; + } + dev->wait_for_migrate_data = FALSE; + spice_char_device_write_to_device(dev); + spice_char_device_read_from_device(dev); + return TRUE; +} diff --git a/server/char-device.h b/server/char-device.h new file mode 100644 index 0000000..7449c6c --- /dev/null +++ b/server/char-device.h @@ -0,0 +1,216 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef CHAR_DEVICE_H_ +#define CHAR_DEVICE_H_ + +#include "spice.h" +#include "red_channel.h" +#include "migration-protocol.h" + +/* + * Shared code for char devices, mainly for flow control. + * + * How to use the api: + * ================== + * device attached: call spice_char_device_state_create + * device detached: call spice_char_device_state_destroy/reset + * + * client connected and associated with a device: spice_char_device_client_add + * client disconnected: spice_char_device_client_remove + * + * Writing to the device + * --------------------- + * Write the data into SpiceCharDeviceWriteBuffer: + * call spice_char_device_write_buffer_get in order to get an appropriate buffer. + * call spice_char_device_write_buffer_add in order to push the buffer to the write queue. + * If you choose not to push the buffer to the device, call + * spice_char_device_write_buffer_release + * + * reading from the device + * ----------------------- + * The callback read_one_msg_from_device (see below) should be implemented + * (using sif->read). + * When the device is ready, this callback is called, and is expected to + * return one message which is addressed to the client, or NULL if the read + * hasn't completed. + * + * calls triggered from the device (qemu): + * -------------------------------------- + * spice_char_device_start + * spice_char_device_stop + * spice_char_device_wakeup (for reading from the device) + */ + +/* + * Note about multiple-clients: + * Multiclients are currently not supported in any of the character devices: + * spicevmc does not allow more than one client (and at least for usb, it should stay this way). + * smartcard code is not compatible with more than one reader. + * The server and guest agent code doesn't distinguish messages from different clients. + * In addition, its current flow control code (e.g., tokens handling) is wrong and doesn't + * take into account the different clients. + * + * Nonetheless, the following code introduces some support for multiple-clients: + * We track the number of tokens for all the clients, and we read from the device + * if one of the clients have enough tokens. For the clients that don't have tokens, + * we queue the messages, till they receive tokens, or till a timeout. + * + * TODO: + * At least for the agent, not all the messages from the device will be directed to all + * the clients (e.g., copy from guest to a specific client). Thus, support for + * client-specific-messages should be added. + * In addition, we should have support for clients that are being connected + * in the middle of a message transfer from the agent to the clients. + * + * */ + +/* buffer that is used for writing to the device */ +typedef struct SpiceCharDeviceWriteBuffer { + RingItem link; + int origin; + RedClient *client; /* The client that sent the message to the device. + NULL if the server created the message */ + + uint8_t *buf; + uint32_t buf_size; + uint32_t buf_used; + uint32_t token_price; + uint32_t refs; +} SpiceCharDeviceWriteBuffer; + +typedef void SpiceCharDeviceMsgToClient; + +typedef struct SpiceCharDeviceCallbacks { + /* + * Messages that are addressed to the client can be queued in case we have + * multiple clients and some of them don't have enough tokens. + */ + + /* reads from the device till reaching a msg that should be sent to the client, + * or till the reading fails */ + SpiceCharDeviceMsgToClient* (*read_one_msg_from_device)(SpiceCharDeviceInstance *sin, + void *opaque); + SpiceCharDeviceMsgToClient* (*ref_msg_to_client)(SpiceCharDeviceMsgToClient *msg, + void *opaque); + void (*unref_msg_to_client)(SpiceCharDeviceMsgToClient *msg, + void *opaque); + void (*send_msg_to_client)(SpiceCharDeviceMsgToClient *msg, + RedClient *client, + void *opaque); /* after this call, the message is unreferenced */ + + /* The cb is called when a predefined number of write buffers were consumed by the + * device */ + void (*send_tokens_to_client)(RedClient *client, uint32_t tokens, void *opaque); + + /* The cb is called when a server (self) message that was addressed to the device, + * has been completely written to it */ + void (*on_free_self_token)(void *opaque); + + /* This cb is called if it is recommanded that a client will be removed + * due to slow flow or due to some other error. + * The called instance should disconnect the client, or at least the corresponding channel */ + void (*remove_client)(RedClient *client, void *opaque); +} SpiceCharDeviceCallbacks; + +SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin, + uint32_t client_tokens_interval, + uint32_t self_tokens, + SpiceCharDeviceCallbacks *cbs, + void *opaque); + +void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *dev, + SpiceCharDeviceInstance *sin); +void spice_char_device_state_destroy(SpiceCharDeviceState *dev); + +void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev); + +/* only one client is supported */ +void spice_char_device_state_migrate_data_marshall(SpiceCharDeviceState *dev, + SpiceMarshaller *m); +void spice_char_device_state_migrate_data_marshall_empty(SpiceMarshaller *m); + +int spice_char_device_state_restore(SpiceCharDeviceState *dev, + SpiceMigrateDataCharDevice *mig_data); + +/* + * Resets write/read queues, and moves that state to being stopped. + * This routine is a workaround for a bad tokens management in the vdagent + * protocol: + * The client tokens' are set only once, when the main channel is initialized. + * Instead, it would have been more appropriate to reset them upon AGEN_CONNECT. + * The client tokens are tracked as part of the SpiceCharDeviceClientState. Thus, + * in order to be backwartd compatible with the client, we need to track the tokens + * event when the agent is detached. We don't destroy the char_device state, and + * instead we just reset it. + * In addition, there is a misshandling of AGENT_TOKENS message in spice-gtk: it + * overrides the amount of tokens, instead of adding the given amount. + * + * todo: change AGENT_CONNECT msg to contain tokens count. + */ +void spice_char_device_reset(SpiceCharDeviceState *dev); + +/* max_send_queue_size = how many messages we can read from the device and enqueue for this client, + * when we have tokens for other clients and no tokens for this one */ +int spice_char_device_client_add(SpiceCharDeviceState *dev, + RedClient *client, + int do_flow_control, + uint32_t max_send_queue_size, + uint32_t num_client_tokens, + uint32_t num_send_tokens, + int wait_for_migrate_data); + +void spice_char_device_client_remove(SpiceCharDeviceState *dev, + RedClient *client); +int spice_char_device_client_exists(SpiceCharDeviceState *dev, + RedClient *client); + +void spice_char_device_start(SpiceCharDeviceState *dev); +void spice_char_device_stop(SpiceCharDeviceState *dev); + +/** Read from device **/ + +void spice_char_device_wakeup(SpiceCharDeviceState *dev); + +void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev, + RedClient *client, + uint32_t tokens); + + +void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev, + RedClient *client, + uint32_t tokens); +/** Write to device **/ + +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev, + RedClient *client, int size); +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get_server_no_token( + SpiceCharDeviceState *dev, int size); + +/* Either add the buffer to the write queue or release it */ +void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *write_buf); +void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *write_buf); + +/* api for specific char devices */ + +SpiceCharDeviceState *spicevmc_device_connect(SpiceCharDeviceInstance *sin, + uint8_t channel_type); +void spicevmc_device_disconnect(SpiceCharDeviceInstance *char_device); + +#endif // CHAR_DEVICE_H_ diff --git a/server/char_device.c b/server/char_device.c deleted file mode 100644 index ae7cb98..0000000 --- a/server/char_device.c +++ /dev/null @@ -1,1035 +0,0 @@ -/* spice-server char device flow control code - - Copyright (C) 2012 Red Hat, Inc. - - Red Hat Authors: - Yonit Halperin <yhalperi@xxxxxxxxxx> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http:www.gnu.org/licenses/>. -*/ - - -#include <config.h> -#include "char_device.h" -#include "red_channel.h" -#include "reds.h" - -#define CHAR_DEVICE_WRITE_TO_TIMEOUT 100 -#define SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT 30000 -#define MAX_POOL_SIZE (10 * 64 * 1024) - -typedef struct SpiceCharDeviceClientState SpiceCharDeviceClientState; -struct SpiceCharDeviceClientState { - RingItem link; - SpiceCharDeviceState *dev; - RedClient *client; - int do_flow_control; - uint64_t num_client_tokens; - uint64_t num_client_tokens_free; /* client messages that were consumed by the device */ - uint64_t num_send_tokens; /* send to client */ - SpiceTimer *wait_for_tokens_timer; - int wait_for_tokens_started; - Ring send_queue; - uint32_t send_queue_size; - uint32_t max_send_queue_size; -}; - -struct SpiceCharDeviceState { - int running; - int active; /* has read/write been performed since the device was started */ - int wait_for_migrate_data; - uint32_t refs; - - Ring write_queue; - Ring write_bufs_pool; - uint64_t cur_pool_size; - SpiceCharDeviceWriteBuffer *cur_write_buf; - uint8_t *cur_write_buf_pos; - SpiceTimer *write_to_dev_timer; - uint64_t num_self_tokens; - - Ring clients; /* list of SpiceCharDeviceClientState */ - uint32_t num_clients; - - uint64_t client_tokens_interval; /* frequency of returning tokens to the client */ - SpiceCharDeviceInstance *sin; - - int during_read_from_device; - int during_write_to_device; - - SpiceCharDeviceCallbacks cbs; - void *opaque; -}; - -enum { - WRITE_BUFFER_ORIGIN_NONE, - WRITE_BUFFER_ORIGIN_CLIENT, - WRITE_BUFFER_ORIGIN_SERVER, - WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN, -}; - -/* Holding references for avoiding access violation if the char device was - * destroyed during a callback */ -static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev); -static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev); -static void spice_char_device_write_buffer_unref(SpiceCharDeviceWriteBuffer *write_buf); - -static void spice_char_dev_write_retry(void *opaque); - -typedef struct SpiceCharDeviceMsgToClientItem { - RingItem link; - SpiceCharDeviceMsgToClient *msg; -} SpiceCharDeviceMsgToClientItem; - -static void spice_char_device_write_buffer_free(SpiceCharDeviceWriteBuffer *buf) -{ - if (buf == NULL) - return; - - free(buf->buf); - free(buf); -} - -static void write_buffers_queue_free(Ring *write_queue) -{ - while (!ring_is_empty(write_queue)) { - RingItem *item = ring_get_tail(write_queue); - SpiceCharDeviceWriteBuffer *buf; - - ring_remove(item); - buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); - spice_char_device_write_buffer_free(buf); - } -} - -static void spice_char_device_write_buffer_pool_add(SpiceCharDeviceState *dev, - SpiceCharDeviceWriteBuffer *buf) -{ - if (buf->refs == 1 && - dev->cur_pool_size < MAX_POOL_SIZE) { - buf->buf_used = 0; - buf->origin = WRITE_BUFFER_ORIGIN_NONE; - buf->client = NULL; - dev->cur_pool_size += buf->buf_size; - ring_add(&dev->write_bufs_pool, &buf->link); - return; - } - - /* Buffer still being used - just unref for the caller */ - spice_char_device_write_buffer_unref(buf); -} - -static void spice_char_device_client_send_queue_free(SpiceCharDeviceState *dev, - SpiceCharDeviceClientState *dev_client) -{ - spice_debug("send_queue_empty %d", ring_is_empty(&dev_client->send_queue)); - while (!ring_is_empty(&dev_client->send_queue)) { - RingItem *item = ring_get_tail(&dev_client->send_queue); - SpiceCharDeviceMsgToClientItem *msg_item = SPICE_CONTAINEROF(item, - SpiceCharDeviceMsgToClientItem, - link); - - ring_remove(item); - dev->cbs.unref_msg_to_client(msg_item->msg, dev->opaque); - free(msg_item); - } - dev_client->num_send_tokens += dev_client->send_queue_size; - dev_client->send_queue_size = 0; -} - -static void spice_char_device_client_free(SpiceCharDeviceState *dev, - SpiceCharDeviceClientState *dev_client) -{ - RingItem *item, *next; - - if (dev_client->wait_for_tokens_timer) { - core->timer_remove(dev_client->wait_for_tokens_timer); - } - - spice_char_device_client_send_queue_free(dev, dev_client); - - /* remove write buffers that are associated with the client */ - spice_debug("write_queue_is_empty %d", ring_is_empty(&dev->write_queue) && !dev->cur_write_buf); - RING_FOREACH_SAFE(item, next, &dev->write_queue) { - SpiceCharDeviceWriteBuffer *write_buf; - - write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); - if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && - write_buf->client == dev_client->client) { - ring_remove(item); - spice_char_device_write_buffer_pool_add(dev, write_buf); - } - } - - if (dev->cur_write_buf && dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && - dev->cur_write_buf->client == dev_client->client) { - dev->cur_write_buf->origin = WRITE_BUFFER_ORIGIN_NONE; - dev->cur_write_buf->client = NULL; - } - - dev->num_clients--; - ring_remove(&dev_client->link); - free(dev_client); -} - -static void spice_char_device_handle_client_overflow(SpiceCharDeviceClientState *dev_client) -{ - SpiceCharDeviceState *dev = dev_client->dev; - spice_printerr("dev %p client %p ", dev, dev_client); - dev->cbs.remove_client(dev_client->client, dev->opaque); -} - -static SpiceCharDeviceClientState *spice_char_device_client_find(SpiceCharDeviceState *dev, - RedClient *client) -{ - RingItem *item; - - RING_FOREACH(item, &dev->clients) { - SpiceCharDeviceClientState *dev_client; - - dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); - if (dev_client->client == client) { - return dev_client; - } - } - return NULL; -} - -/*************************** - * Reading from the device * - **************************/ - -static void device_client_wait_for_tokens_timeout(void *opaque) -{ - SpiceCharDeviceClientState *dev_client = opaque; - - spice_char_device_handle_client_overflow(dev_client); -} - -static int spice_char_device_can_send_to_client(SpiceCharDeviceClientState *dev_client) -{ - return !dev_client->do_flow_control || dev_client->num_send_tokens; -} - -static uint64_t spice_char_device_max_send_tokens(SpiceCharDeviceState *dev) -{ - RingItem *item; - uint64_t max = 0; - - RING_FOREACH(item, &dev->clients) { - SpiceCharDeviceClientState *dev_client; - - dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); - - if (!dev_client->do_flow_control) { - max = ~0; - break; - } - - if (dev_client->num_send_tokens > max) { - max = dev_client->num_send_tokens; - } - } - return max; -} - -static void spice_char_device_add_msg_to_client_queue(SpiceCharDeviceClientState *dev_client, - SpiceCharDeviceMsgToClient *msg) -{ - SpiceCharDeviceState *dev = dev_client->dev; - SpiceCharDeviceMsgToClientItem *msg_item; - - if (dev_client->send_queue_size >= dev_client->max_send_queue_size) { - spice_char_device_handle_client_overflow(dev_client); - return; - } - - msg_item = spice_new0(SpiceCharDeviceMsgToClientItem, 1); - msg_item->msg = dev->cbs.ref_msg_to_client(msg, dev->opaque); - ring_add(&dev_client->send_queue, &msg_item->link); - dev_client->send_queue_size++; - if (!dev_client->wait_for_tokens_started) { - core->timer_start(dev_client->wait_for_tokens_timer, - SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT); - dev_client->wait_for_tokens_started = TRUE; - } -} - -static void spice_char_device_send_msg_to_clients(SpiceCharDeviceState *dev, - SpiceCharDeviceMsgToClient *msg) -{ - RingItem *item, *next; - - RING_FOREACH_SAFE(item, next, &dev->clients) { - SpiceCharDeviceClientState *dev_client; - - dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); - if (spice_char_device_can_send_to_client(dev_client)) { - dev_client->num_send_tokens--; - spice_assert(ring_is_empty(&dev_client->send_queue)); - dev->cbs.send_msg_to_client(msg, dev_client->client, dev->opaque); - - /* don't refer to dev_client anymore, it may have been released */ - } else { - spice_char_device_add_msg_to_client_queue(dev_client, msg); - } - } -} - -static int spice_char_device_read_from_device(SpiceCharDeviceState *dev) -{ - uint64_t max_send_tokens; - int did_read = FALSE; - - if (!dev->running || dev->wait_for_migrate_data || !dev->sin) { - return FALSE; - } - - /* There are 2 scenarios where we can get called recursively: - * 1) spice-vmc vmc_read triggering flush of throttled data, recalling wakeup - * (virtio) - * 2) in case of sending messages to the client, and unreferencing the - * msg, we trigger another read. - */ - if (dev->during_read_from_device++ > 0) { - return FALSE; - } - - max_send_tokens = spice_char_device_max_send_tokens(dev); - spice_char_device_state_ref(dev); - /* - * Reading from the device only in case at least one of the clients have a free token. - * All messages will be discarded if no client is attached to the device - */ - while ((max_send_tokens || ring_is_empty(&dev->clients)) && dev->running) { - SpiceCharDeviceMsgToClient *msg; - - msg = dev->cbs.read_one_msg_from_device(dev->sin, dev->opaque); - if (!msg) { - if (dev->during_read_from_device > 1) { - dev->during_read_from_device = 1; - continue; /* a wakeup might have been called during the read - - make sure it doesn't get lost */ - } - break; - } - did_read = TRUE; - spice_char_device_send_msg_to_clients(dev, msg); - dev->cbs.unref_msg_to_client(msg, dev->opaque); - max_send_tokens--; - } - dev->during_read_from_device = 0; - if (dev->running) { - dev->active = dev->active || did_read; - } - spice_char_device_state_unref(dev); - return did_read; -} - -static void spice_char_device_client_send_queue_push(SpiceCharDeviceClientState *dev_client) -{ - RingItem *item; - while ((item = ring_get_tail(&dev_client->send_queue)) && - spice_char_device_can_send_to_client(dev_client)) { - SpiceCharDeviceMsgToClientItem *msg_item; - - msg_item = SPICE_CONTAINEROF(item, SpiceCharDeviceMsgToClientItem, link); - ring_remove(item); - - dev_client->num_send_tokens--; - dev_client->dev->cbs.send_msg_to_client(msg_item->msg, - dev_client->client, - dev_client->dev->opaque); - dev_client->dev->cbs.unref_msg_to_client(msg_item->msg, dev_client->dev->opaque); - dev_client->send_queue_size--; - free(msg_item); - } -} - -static void spice_char_device_send_to_client_tokens_absorb(SpiceCharDeviceClientState *dev_client, - uint32_t tokens) -{ - dev_client->num_send_tokens += tokens; - - if (dev_client->send_queue_size) { - spice_assert(dev_client->num_send_tokens == tokens); - spice_char_device_client_send_queue_push(dev_client); - } - - if (spice_char_device_can_send_to_client(dev_client)) { - core->timer_cancel(dev_client->wait_for_tokens_timer); - dev_client->wait_for_tokens_started = FALSE; - spice_char_device_read_from_device(dev_client->dev); - } else if (dev_client->send_queue_size) { - core->timer_start(dev_client->wait_for_tokens_timer, - SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT); - dev_client->wait_for_tokens_started = TRUE; - } -} - -void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev, - RedClient *client, - uint32_t tokens) -{ - SpiceCharDeviceClientState *dev_client; - - dev_client = spice_char_device_client_find(dev, client); - - if (!dev_client) { - spice_error("client wasn't found dev %p client %p", dev, client); - return; - } - spice_char_device_send_to_client_tokens_absorb(dev_client, tokens); -} - -void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev, - RedClient *client, - uint32_t tokens) -{ - SpiceCharDeviceClientState *dev_client; - - dev_client = spice_char_device_client_find(dev, client); - - if (!dev_client) { - spice_error("client wasn't found dev %p client %p", dev, client); - return; - } - - dev_client->num_send_tokens = 0; - spice_char_device_send_to_client_tokens_absorb(dev_client, tokens); -} - -/************************** - * Writing to the device * -***************************/ - -static void spice_char_device_client_tokens_add(SpiceCharDeviceState *dev, - SpiceCharDeviceClientState *dev_client, - uint32_t num_tokens) -{ - if (!dev_client->do_flow_control) { - return; - } - if (num_tokens > 1) { - spice_debug("#tokens > 1 (=%u)", num_tokens); - } - dev_client->num_client_tokens_free += num_tokens; - if (dev_client->num_client_tokens_free >= dev->client_tokens_interval) { - uint32_t tokens = dev_client->num_client_tokens_free; - - dev_client->num_client_tokens += dev_client->num_client_tokens_free; - dev_client->num_client_tokens_free = 0; - dev->cbs.send_tokens_to_client(dev_client->client, - tokens, - dev->opaque); - } -} - -static int spice_char_device_write_to_device(SpiceCharDeviceState *dev) -{ - SpiceCharDeviceInterface *sif; - int total = 0; - int n; - - if (!dev->running || dev->wait_for_migrate_data || !dev->sin) { - return 0; - } - - /* protect against recursion with spice_char_device_wakeup */ - if (dev->during_write_to_device++ > 0) { - return 0; - } - - spice_char_device_state_ref(dev); - - if (dev->write_to_dev_timer) { - core->timer_cancel(dev->write_to_dev_timer); - } - - sif = SPICE_CONTAINEROF(dev->sin->base.sif, SpiceCharDeviceInterface, base); - while (dev->running) { - uint32_t write_len; - - if (!dev->cur_write_buf) { - RingItem *item = ring_get_tail(&dev->write_queue); - if (!item) { - break; - } - dev->cur_write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); - dev->cur_write_buf_pos = dev->cur_write_buf->buf; - ring_remove(item); - } - - write_len = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used - - dev->cur_write_buf_pos; - n = sif->write(dev->sin, dev->cur_write_buf_pos, write_len); - if (n <= 0) { - if (dev->during_write_to_device > 1) { - dev->during_write_to_device = 1; - continue; /* a wakeup might have been called during the write - - make sure it doesn't get lost */ - } - break; - } - total += n; - write_len -= n; - if (!write_len) { - SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf; - dev->cur_write_buf = NULL; - spice_char_device_write_buffer_release(dev, release_buf); - continue; - } - dev->cur_write_buf_pos += n; - } - /* retry writing as long as the write queue is not empty */ - if (dev->running) { - if (dev->cur_write_buf) { - if (dev->write_to_dev_timer) { - core->timer_start(dev->write_to_dev_timer, - CHAR_DEVICE_WRITE_TO_TIMEOUT); - } - } else { - spice_assert(ring_is_empty(&dev->write_queue)); - } - dev->active = dev->active || total; - } - dev->during_write_to_device = 0; - spice_char_device_state_unref(dev); - return total; -} - -static void spice_char_dev_write_retry(void *opaque) -{ - SpiceCharDeviceState *dev = opaque; - - if (dev->write_to_dev_timer) { - core->timer_cancel(dev->write_to_dev_timer); - } - spice_char_device_write_to_device(dev); -} - -static SpiceCharDeviceWriteBuffer *__spice_char_device_write_buffer_get( - SpiceCharDeviceState *dev, RedClient *client, - int size, int origin, int migrated_data_tokens) -{ - RingItem *item; - SpiceCharDeviceWriteBuffer *ret; - - if (origin == WRITE_BUFFER_ORIGIN_SERVER && !dev->num_self_tokens) { - return NULL; - } - - if ((item = ring_get_tail(&dev->write_bufs_pool))) { - ret = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); - ring_remove(item); - dev->cur_pool_size -= ret->buf_size; - } else { - ret = spice_new0(SpiceCharDeviceWriteBuffer, 1); - } - - spice_assert(!ret->buf_used); - - if (ret->buf_size < size) { - ret->buf = spice_realloc(ret->buf, size); - ret->buf_size = size; - } - ret->origin = origin; - - if (origin == WRITE_BUFFER_ORIGIN_CLIENT) { - spice_assert(client); - SpiceCharDeviceClientState *dev_client = spice_char_device_client_find(dev, client); - if (dev_client) { - if (!migrated_data_tokens && - dev_client->do_flow_control && !dev_client->num_client_tokens) { - spice_printerr("token violation: dev %p client %p", dev, client); - spice_char_device_handle_client_overflow(dev_client); - goto error; - } - ret->client = client; - if (!migrated_data_tokens && dev_client->do_flow_control) { - dev_client->num_client_tokens--; - } - } else { - /* it is possible that the client was removed due to send tokens underflow, but - * the caller still receive messages from the client */ - spice_printerr("client not found: dev %p client %p", dev, client); - goto error; - } - } else if (origin == WRITE_BUFFER_ORIGIN_SERVER) { - dev->num_self_tokens--; - } - - ret->token_price = migrated_data_tokens ? migrated_data_tokens : 1; - ret->refs = 1; - return ret; -error: - dev->cur_pool_size += ret->buf_size; - ring_add(&dev->write_bufs_pool, &ret->link); - return NULL; -} - -SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev, - RedClient *client, - int size) -{ - return __spice_char_device_write_buffer_get(dev, client, size, - client ? WRITE_BUFFER_ORIGIN_CLIENT : WRITE_BUFFER_ORIGIN_SERVER, - 0); -} - -SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get_server_no_token( - SpiceCharDeviceState *dev, int size) -{ - return __spice_char_device_write_buffer_get(dev, NULL, size, - WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN, 0); -} - -static SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_ref(SpiceCharDeviceWriteBuffer *write_buf) -{ - spice_assert(write_buf); - - write_buf->refs++; - return write_buf; -} - -static void spice_char_device_write_buffer_unref(SpiceCharDeviceWriteBuffer *write_buf) -{ - spice_assert(write_buf); - - write_buf->refs--; - if (write_buf->refs == 0) - spice_char_device_write_buffer_free(write_buf); -} - -void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev, - SpiceCharDeviceWriteBuffer *write_buf) -{ - spice_assert(dev); - /* caller shouldn't add buffers for client that was removed */ - if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && - !spice_char_device_client_find(dev, write_buf->client)) { - spice_printerr("client not found: dev %p client %p", dev, write_buf->client); - spice_char_device_write_buffer_pool_add(dev, write_buf); - return; - } - - ring_add(&dev->write_queue, &write_buf->link); - spice_char_device_write_to_device(dev); -} - -void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev, - SpiceCharDeviceWriteBuffer *write_buf) -{ - int buf_origin = write_buf->origin; - uint32_t buf_token_price = write_buf->token_price; - RedClient *client = write_buf->client; - - spice_assert(!ring_item_is_linked(&write_buf->link)); - if (!dev) { - spice_printerr("no device. write buffer is freed"); - spice_char_device_write_buffer_free(write_buf); - return; - } - - spice_assert(dev->cur_write_buf != write_buf); - - spice_char_device_write_buffer_pool_add(dev, write_buf); - if (buf_origin == WRITE_BUFFER_ORIGIN_CLIENT) { - SpiceCharDeviceClientState *dev_client; - - spice_assert(client); - dev_client = spice_char_device_client_find(dev, client); - /* when a client is removed, we remove all the buffers that are associated with it */ - spice_assert(dev_client); - spice_char_device_client_tokens_add(dev, dev_client, buf_token_price); - } else if (buf_origin == WRITE_BUFFER_ORIGIN_SERVER) { - dev->num_self_tokens++; - if (dev->cbs.on_free_self_token) { - dev->cbs.on_free_self_token(dev->opaque); - } - } -} - -/******************************** - * char_device_state management * - ********************************/ - -SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin, - uint32_t client_tokens_interval, - uint32_t self_tokens, - SpiceCharDeviceCallbacks *cbs, - void *opaque) -{ - SpiceCharDeviceState *char_dev; - SpiceCharDeviceInterface *sif; - - spice_assert(sin); - spice_assert(cbs->read_one_msg_from_device && cbs->ref_msg_to_client && - cbs->unref_msg_to_client && cbs->send_msg_to_client && - cbs->send_tokens_to_client && cbs->remove_client); - - char_dev = spice_new0(SpiceCharDeviceState, 1); - char_dev->sin = sin; - char_dev->cbs = *cbs; - char_dev->opaque = opaque; - char_dev->client_tokens_interval = client_tokens_interval; - char_dev->num_self_tokens = self_tokens; - - ring_init(&char_dev->write_queue); - ring_init(&char_dev->write_bufs_pool); - ring_init(&char_dev->clients); - - sif = SPICE_CONTAINEROF(char_dev->sin->base.sif, SpiceCharDeviceInterface, base); - if (sif->base.minor_version <= 2 || - !(sif->flags & SPICE_CHAR_DEVICE_NOTIFY_WRITABLE)) { - char_dev->write_to_dev_timer = core->timer_add(spice_char_dev_write_retry, char_dev); - if (!char_dev->write_to_dev_timer) { - spice_error("failed creating char dev write timer"); - } - } - - char_dev->refs = 1; - sin->st = char_dev; - spice_debug("sin %p dev_state %p", sin, char_dev); - return char_dev; -} - -void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *state, - SpiceCharDeviceInstance *sin) -{ - spice_debug("sin %p dev_state %p", sin, state); - state->sin = sin; - sin->st = state; -} - -void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev) -{ - return dev->opaque; -} - -static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev) -{ - char_dev->refs++; -} - -static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev) -{ - /* The refs field protects the char_dev from being deallocated in - * case spice_char_device_state_destroy has been called - * during a callabck, and we might still access the char_dev afterwards. - * spice_char_device_state_unref is always coupled with a preceding - * spice_char_device_state_ref. Here, refs can turn 0 - * only when spice_char_device_state_destroy is called in between - * the calls to spice_char_device_state_ref and spice_char_device_state_unref.*/ - if (!--char_dev->refs) { - free(char_dev); - } -} - -void spice_char_device_state_destroy(SpiceCharDeviceState *char_dev) -{ - reds_on_char_device_state_destroy(char_dev); - if (char_dev->write_to_dev_timer) { - core->timer_remove(char_dev->write_to_dev_timer); - char_dev->write_to_dev_timer = NULL; - } - write_buffers_queue_free(&char_dev->write_queue); - write_buffers_queue_free(&char_dev->write_bufs_pool); - char_dev->cur_pool_size = 0; - spice_char_device_write_buffer_free(char_dev->cur_write_buf); - char_dev->cur_write_buf = NULL; - - while (!ring_is_empty(&char_dev->clients)) { - RingItem *item = ring_get_tail(&char_dev->clients); - SpiceCharDeviceClientState *dev_client; - - dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); - spice_char_device_client_free(char_dev, dev_client); - } - char_dev->running = FALSE; - - spice_char_device_state_unref(char_dev); -} - -int spice_char_device_client_add(SpiceCharDeviceState *dev, - RedClient *client, - int do_flow_control, - uint32_t max_send_queue_size, - uint32_t num_client_tokens, - uint32_t num_send_tokens, - int wait_for_migrate_data) -{ - SpiceCharDeviceClientState *dev_client; - - spice_assert(dev); - spice_assert(client); - - if (wait_for_migrate_data && (dev->num_clients > 0 || dev->active)) { - spice_warning("can't restore device %p from migration data. The device " - "has already been active", dev); - return FALSE; - } - - dev->wait_for_migrate_data = wait_for_migrate_data; - - spice_debug("dev_state %p client %p", dev, client); - dev_client = spice_new0(SpiceCharDeviceClientState, 1); - dev_client->dev = dev; - dev_client->client = client; - ring_init(&dev_client->send_queue); - dev_client->send_queue_size = 0; - dev_client->max_send_queue_size = max_send_queue_size; - dev_client->do_flow_control = do_flow_control; - if (do_flow_control) { - dev_client->wait_for_tokens_timer = core->timer_add(device_client_wait_for_tokens_timeout, - dev_client); - if (!dev_client->wait_for_tokens_timer) { - spice_error("failed to create wait for tokens timer"); - } - dev_client->num_client_tokens = num_client_tokens; - dev_client->num_send_tokens = num_send_tokens; - } else { - dev_client->num_client_tokens = ~0; - dev_client->num_send_tokens = ~0; - } - ring_add(&dev->clients, &dev_client->link); - dev->num_clients++; - /* Now that we have a client, forward any pending device data */ - spice_char_device_wakeup(dev); - return TRUE; -} - -void spice_char_device_client_remove(SpiceCharDeviceState *dev, - RedClient *client) -{ - SpiceCharDeviceClientState *dev_client; - - spice_debug("dev_state %p client %p", dev, client); - dev_client = spice_char_device_client_find(dev, client); - - if (!dev_client) { - spice_error("client wasn't found"); - return; - } - spice_char_device_client_free(dev, dev_client); - if (dev->wait_for_migrate_data) { - spice_assert(dev->num_clients == 0); - dev->wait_for_migrate_data = FALSE; - spice_char_device_read_from_device(dev); - } - - if (dev->num_clients == 0) { - spice_debug("client removed, memory pool will be freed (%lu bytes)", dev->cur_pool_size); - write_buffers_queue_free(&dev->write_bufs_pool); - dev->cur_pool_size = 0; - } -} - -int spice_char_device_client_exists(SpiceCharDeviceState *dev, - RedClient *client) -{ - return (spice_char_device_client_find(dev, client) != NULL); -} - -void spice_char_device_start(SpiceCharDeviceState *dev) -{ - spice_debug("dev_state %p", dev); - dev->running = TRUE; - spice_char_device_state_ref(dev); - while (spice_char_device_write_to_device(dev) || - spice_char_device_read_from_device(dev)); - spice_char_device_state_unref(dev); -} - -void spice_char_device_stop(SpiceCharDeviceState *dev) -{ - spice_debug("dev_state %p", dev); - dev->running = FALSE; - dev->active = FALSE; - if (dev->write_to_dev_timer) { - core->timer_cancel(dev->write_to_dev_timer); - } -} - -void spice_char_device_reset(SpiceCharDeviceState *dev) -{ - RingItem *client_item; - - spice_char_device_stop(dev); - dev->wait_for_migrate_data = FALSE; - spice_debug("dev_state %p", dev); - while (!ring_is_empty(&dev->write_queue)) { - RingItem *item = ring_get_tail(&dev->write_queue); - SpiceCharDeviceWriteBuffer *buf; - - ring_remove(item); - buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); - /* tracking the tokens */ - spice_char_device_write_buffer_release(dev, buf); - } - if (dev->cur_write_buf) { - SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf; - - dev->cur_write_buf = NULL; - spice_char_device_write_buffer_release(dev, release_buf); - } - - RING_FOREACH(client_item, &dev->clients) { - SpiceCharDeviceClientState *dev_client; - - dev_client = SPICE_CONTAINEROF(client_item, SpiceCharDeviceClientState, link); - spice_char_device_client_send_queue_free(dev, dev_client); - } - dev->sin = NULL; -} - -void spice_char_device_wakeup(SpiceCharDeviceState *dev) -{ - spice_char_device_write_to_device(dev); - spice_char_device_read_from_device(dev); -} - -/************* - * Migration * - * **********/ - -void spice_char_device_state_migrate_data_marshall_empty(SpiceMarshaller *m) -{ - SpiceMigrateDataCharDevice *mig_data; - - spice_debug(NULL); - mig_data = (SpiceMigrateDataCharDevice *)spice_marshaller_reserve_space(m, - sizeof(*mig_data)); - memset(mig_data, 0, sizeof(*mig_data)); - mig_data->version = SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION; - mig_data->connected = FALSE; -} - -static void migrate_data_marshaller_write_buffer_free(uint8_t *data, void *opaque) -{ - SpiceCharDeviceWriteBuffer *write_buf = (SpiceCharDeviceWriteBuffer *)opaque; - - spice_char_device_write_buffer_unref(write_buf); -} - -void spice_char_device_state_migrate_data_marshall(SpiceCharDeviceState *dev, - SpiceMarshaller *m) -{ - SpiceCharDeviceClientState *client_state; - RingItem *item; - uint32_t *write_to_dev_size_ptr; - uint32_t *write_to_dev_tokens_ptr; - SpiceMarshaller *m2; - - /* multi-clients are not supported */ - spice_assert(dev->num_clients == 1); - client_state = SPICE_CONTAINEROF(ring_get_tail(&dev->clients), - SpiceCharDeviceClientState, - link); - /* FIXME: if there were more than one client before the marshalling, - * it is possible that the send_queue_size > 0, and the send data - * should be migrated as well */ - spice_assert(client_state->send_queue_size == 0); - spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION); - spice_marshaller_add_uint8(m, 1); /* connected */ - spice_marshaller_add_uint32(m, client_state->num_client_tokens); - spice_marshaller_add_uint32(m, client_state->num_send_tokens); - write_to_dev_size_ptr = (uint32_t *)spice_marshaller_reserve_space(m, sizeof(uint32_t)); - write_to_dev_tokens_ptr = (uint32_t *)spice_marshaller_reserve_space(m, sizeof(uint32_t)); - *write_to_dev_size_ptr = 0; - *write_to_dev_tokens_ptr = 0; - - m2 = spice_marshaller_get_ptr_submarshaller(m, 0); - if (dev->cur_write_buf) { - uint32_t buf_remaining = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used - - dev->cur_write_buf_pos; - spice_marshaller_add_ref_full(m2, dev->cur_write_buf_pos, buf_remaining, - migrate_data_marshaller_write_buffer_free, - spice_char_device_write_buffer_ref(dev->cur_write_buf) - ); - *write_to_dev_size_ptr += buf_remaining; - if (dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT) { - spice_assert(dev->cur_write_buf->client == client_state->client); - (*write_to_dev_tokens_ptr) += dev->cur_write_buf->token_price; - } - } - - RING_FOREACH_REVERSED(item, &dev->write_queue) { - SpiceCharDeviceWriteBuffer *write_buf; - - write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); - spice_marshaller_add_ref_full(m2, write_buf->buf, write_buf->buf_used, - migrate_data_marshaller_write_buffer_free, - spice_char_device_write_buffer_ref(write_buf) - ); - *write_to_dev_size_ptr += write_buf->buf_used; - if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT) { - spice_assert(write_buf->client == client_state->client); - (*write_to_dev_tokens_ptr) += write_buf->token_price; - } - } - spice_debug("migration data dev %p: write_queue size %u tokens %u", - dev, *write_to_dev_size_ptr, *write_to_dev_tokens_ptr); -} - -int spice_char_device_state_restore(SpiceCharDeviceState *dev, - SpiceMigrateDataCharDevice *mig_data) -{ - SpiceCharDeviceClientState *client_state; - uint32_t client_tokens_window; - - spice_assert(dev->num_clients == 1 && dev->wait_for_migrate_data); - - client_state = SPICE_CONTAINEROF(ring_get_tail(&dev->clients), - SpiceCharDeviceClientState, - link); - if (mig_data->version > SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION) { - spice_error("dev %p error: migration data version %u is bigger than self %u", - dev, mig_data->version, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION); - return FALSE; - } - spice_assert(!dev->cur_write_buf && ring_is_empty(&dev->write_queue)); - spice_assert(mig_data->connected); - - client_tokens_window = client_state->num_client_tokens; /* initial state of tokens */ - client_state->num_client_tokens = mig_data->num_client_tokens; - /* assumption: client_tokens_window stays the same across severs */ - client_state->num_client_tokens_free = client_tokens_window - - mig_data->num_client_tokens - - mig_data->write_num_client_tokens; - client_state->num_send_tokens = mig_data->num_send_tokens; - - if (mig_data->write_size > 0) { - if (mig_data->write_num_client_tokens) { - dev->cur_write_buf = - __spice_char_device_write_buffer_get(dev, client_state->client, - mig_data->write_size, WRITE_BUFFER_ORIGIN_CLIENT, - mig_data->write_num_client_tokens); - } else { - dev->cur_write_buf = - __spice_char_device_write_buffer_get(dev, NULL, - mig_data->write_size, WRITE_BUFFER_ORIGIN_SERVER, 0); - } - /* the first write buffer contains all the data that was saved for migration */ - memcpy(dev->cur_write_buf->buf, - ((uint8_t *)mig_data) + mig_data->write_data_ptr - sizeof(SpiceMigrateDataHeader), - mig_data->write_size); - dev->cur_write_buf->buf_used = mig_data->write_size; - dev->cur_write_buf_pos = dev->cur_write_buf->buf; - } - dev->wait_for_migrate_data = FALSE; - spice_char_device_write_to_device(dev); - spice_char_device_read_from_device(dev); - return TRUE; -} diff --git a/server/char_device.h b/server/char_device.h deleted file mode 100644 index 55d1ee6..0000000 --- a/server/char_device.h +++ /dev/null @@ -1,216 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009-2015 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifndef __CHAR_DEVICE_H__ -#define __CHAR_DEVICE_H__ - -#include "spice.h" -#include "red_channel.h" -#include "migration_protocol.h" - -/* - * Shared code for char devices, mainly for flow control. - * - * How to use the api: - * ================== - * device attached: call spice_char_device_state_create - * device detached: call spice_char_device_state_destroy/reset - * - * client connected and associated with a device: spice_char_device_client_add - * client disconnected: spice_char_device_client_remove - * - * Writing to the device - * --------------------- - * Write the data into SpiceCharDeviceWriteBuffer: - * call spice_char_device_write_buffer_get in order to get an appropriate buffer. - * call spice_char_device_write_buffer_add in order to push the buffer to the write queue. - * If you choose not to push the buffer to the device, call - * spice_char_device_write_buffer_release - * - * reading from the device - * ----------------------- - * The callback read_one_msg_from_device (see below) should be implemented - * (using sif->read). - * When the device is ready, this callback is called, and is expected to - * return one message which is addressed to the client, or NULL if the read - * hasn't completed. - * - * calls triggered from the device (qemu): - * -------------------------------------- - * spice_char_device_start - * spice_char_device_stop - * spice_char_device_wakeup (for reading from the device) - */ - -/* - * Note about multiple-clients: - * Multiclients are currently not supported in any of the character devices: - * spicevmc does not allow more than one client (and at least for usb, it should stay this way). - * smartcard code is not compatible with more than one reader. - * The server and guest agent code doesn't distinguish messages from different clients. - * In addition, its current flow control code (e.g., tokens handling) is wrong and doesn't - * take into account the different clients. - * - * Nonetheless, the following code introduces some support for multiple-clients: - * We track the number of tokens for all the clients, and we read from the device - * if one of the clients have enough tokens. For the clients that don't have tokens, - * we queue the messages, till they receive tokens, or till a timeout. - * - * TODO: - * At least for the agent, not all the messages from the device will be directed to all - * the clients (e.g., copy from guest to a specific client). Thus, support for - * client-specific-messages should be added. - * In addition, we should have support for clients that are being connected - * in the middle of a message transfer from the agent to the clients. - * - * */ - -/* buffer that is used for writing to the device */ -typedef struct SpiceCharDeviceWriteBuffer { - RingItem link; - int origin; - RedClient *client; /* The client that sent the message to the device. - NULL if the server created the message */ - - uint8_t *buf; - uint32_t buf_size; - uint32_t buf_used; - uint32_t token_price; - uint32_t refs; -} SpiceCharDeviceWriteBuffer; - -typedef void SpiceCharDeviceMsgToClient; - -typedef struct SpiceCharDeviceCallbacks { - /* - * Messages that are addressed to the client can be queued in case we have - * multiple clients and some of them don't have enough tokens. - */ - - /* reads from the device till reaching a msg that should be sent to the client, - * or till the reading fails */ - SpiceCharDeviceMsgToClient* (*read_one_msg_from_device)(SpiceCharDeviceInstance *sin, - void *opaque); - SpiceCharDeviceMsgToClient* (*ref_msg_to_client)(SpiceCharDeviceMsgToClient *msg, - void *opaque); - void (*unref_msg_to_client)(SpiceCharDeviceMsgToClient *msg, - void *opaque); - void (*send_msg_to_client)(SpiceCharDeviceMsgToClient *msg, - RedClient *client, - void *opaque); /* after this call, the message is unreferenced */ - - /* The cb is called when a predefined number of write buffers were consumed by the - * device */ - void (*send_tokens_to_client)(RedClient *client, uint32_t tokens, void *opaque); - - /* The cb is called when a server (self) message that was addressed to the device, - * has been completely written to it */ - void (*on_free_self_token)(void *opaque); - - /* This cb is called if it is recommanded that a client will be removed - * due to slow flow or due to some other error. - * The called instance should disconnect the client, or at least the corresponding channel */ - void (*remove_client)(RedClient *client, void *opaque); -} SpiceCharDeviceCallbacks; - -SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin, - uint32_t client_tokens_interval, - uint32_t self_tokens, - SpiceCharDeviceCallbacks *cbs, - void *opaque); - -void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *dev, - SpiceCharDeviceInstance *sin); -void spice_char_device_state_destroy(SpiceCharDeviceState *dev); - -void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev); - -/* only one client is supported */ -void spice_char_device_state_migrate_data_marshall(SpiceCharDeviceState *dev, - SpiceMarshaller *m); -void spice_char_device_state_migrate_data_marshall_empty(SpiceMarshaller *m); - -int spice_char_device_state_restore(SpiceCharDeviceState *dev, - SpiceMigrateDataCharDevice *mig_data); - -/* - * Resets write/read queues, and moves that state to being stopped. - * This routine is a workaround for a bad tokens management in the vdagent - * protocol: - * The client tokens' are set only once, when the main channel is initialized. - * Instead, it would have been more appropriate to reset them upon AGEN_CONNECT. - * The client tokens are tracked as part of the SpiceCharDeviceClientState. Thus, - * in order to be backwartd compatible with the client, we need to track the tokens - * event when the agent is detached. We don't destroy the char_device state, and - * instead we just reset it. - * In addition, there is a misshandling of AGENT_TOKENS message in spice-gtk: it - * overrides the amount of tokens, instead of adding the given amount. - * - * todo: change AGENT_CONNECT msg to contain tokens count. - */ -void spice_char_device_reset(SpiceCharDeviceState *dev); - -/* max_send_queue_size = how many messages we can read from the device and enqueue for this client, - * when we have tokens for other clients and no tokens for this one */ -int spice_char_device_client_add(SpiceCharDeviceState *dev, - RedClient *client, - int do_flow_control, - uint32_t max_send_queue_size, - uint32_t num_client_tokens, - uint32_t num_send_tokens, - int wait_for_migrate_data); - -void spice_char_device_client_remove(SpiceCharDeviceState *dev, - RedClient *client); -int spice_char_device_client_exists(SpiceCharDeviceState *dev, - RedClient *client); - -void spice_char_device_start(SpiceCharDeviceState *dev); -void spice_char_device_stop(SpiceCharDeviceState *dev); - -/** Read from device **/ - -void spice_char_device_wakeup(SpiceCharDeviceState *dev); - -void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev, - RedClient *client, - uint32_t tokens); - - -void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev, - RedClient *client, - uint32_t tokens); -/** Write to device **/ - -SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev, - RedClient *client, int size); -SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get_server_no_token( - SpiceCharDeviceState *dev, int size); - -/* Either add the buffer to the write queue or release it */ -void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev, - SpiceCharDeviceWriteBuffer *write_buf); -void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev, - SpiceCharDeviceWriteBuffer *write_buf); - -/* api for specific char devices */ - -SpiceCharDeviceState *spicevmc_device_connect(SpiceCharDeviceInstance *sin, - uint8_t channel_type); -void spicevmc_device_disconnect(SpiceCharDeviceInstance *char_device); - -#endif // __CHAR_DEVICE_H__ diff --git a/server/dcc-encoders.h b/server/dcc-encoders.h index 5de66f7..a0a5c64 100644 --- a/server/dcc-encoders.h +++ b/server/dcc-encoders.h @@ -23,14 +23,14 @@ #include "common/quic.h" #include "red_channel.h" #include "red_parse_qxl.h" -#include "spice_image_cache.h" -#include "glz_encoder_dictionary.h" -#include "glz_encoder.h" -#include "jpeg_encoder.h" +#include "image-cache.h" +#include "glz-encoder-dict.h" +#include "glz-encoder.h" +#include "jpeg-encoder.h" #ifdef USE_LZ4 #include "lz4_encoder.h" #endif -#include "zlib_encoder.h" +#include "zlib-encoder.h" typedef struct RedCompressBuf RedCompressBuf; typedef struct GlzDrawableInstanceItem GlzDrawableInstanceItem; diff --git a/server/display-channel.h b/server/display-channel.h index a990e09..50f6c5e 100644 --- a/server/display-channel.h +++ b/server/display-channel.h @@ -25,22 +25,22 @@ #include "reds_stream.h" #include "cache-item.h" #include "pixmap-cache.h" -#include "reds_sw_canvas.h" +#include "sw-canvas.h" #include "stat.h" #include "reds.h" -#include "mjpeg_encoder.h" -#include "red_memslots.h" +#include "mjpeg-encoder.h" +#include "memslot.h" #include "red_parse_qxl.h" #include "red_record_qxl.h" #include "demarshallers.h" #include "red_channel.h" #include "red_dispatcher.h" #include "dispatcher.h" -#include "main_channel.h" -#include "migration_protocol.h" -#include "main_dispatcher.h" +#include "main-channel.h" +#include "migration-protocol.h" +#include "main-dispatcher.h" #include "spice_bitmap_utils.h" -#include "spice_image_cache.h" +#include "image-cache.h" #include "utils.h" #include "tree.h" #include "stream.h" diff --git a/server/glz-encoder-dict.c b/server/glz-encoder-dict.c new file mode 100644 index 0000000..1fd6753 --- /dev/null +++ b/server/glz-encoder-dict.c @@ -0,0 +1,633 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pthread.h> +#include <string.h> +#include <stdio.h> + +#include "glz-encoder-dict.h" +#include "glz-encoder-priv.h" + +/* turning all used images to free ones. If they are alive, calling the free_image callback for + each one */ +static inline void __glz_dictionary_window_reset_images(SharedDictionary *dict) +{ + WindowImage *tmp; + + while (dict->window.used_images_head) { + tmp = dict->window.used_images_head; + dict->window.used_images_head = dict->window.used_images_head->next; + if (tmp->is_alive) { + dict->cur_usr->free_image(dict->cur_usr, tmp->usr_context); + } + tmp->next = dict->window.free_images; + tmp->is_alive = FALSE; + dict->window.free_images = tmp; + } + dict->window.used_images_tail = NULL; +} + +/* allocate window fields (no reset)*/ +static int glz_dictionary_window_create(SharedDictionary *dict, uint32_t size) +{ + if (size > LZ_MAX_WINDOW_SIZE) { + return FALSE; + } + + dict->window.size_limit = size; + dict->window.segs = (WindowImageSegment *)( + dict->cur_usr->malloc(dict->cur_usr, sizeof(WindowImageSegment) * INIT_IMAGE_SEGS_NUM)); + + if (!dict->window.segs) { + return FALSE; + } + + dict->window.segs_quota = INIT_IMAGE_SEGS_NUM; + + dict->window.encoders_heads = (uint32_t *)dict->cur_usr->malloc(dict->cur_usr, + sizeof(uint32_t) * dict->max_encoders); + + if (!dict->window.encoders_heads) { + dict->cur_usr->free(dict->cur_usr, dict->window.segs); + return FALSE; + } + + dict->window.used_images_head = NULL; + dict->window.used_images_tail = NULL; + dict->window.free_images = NULL; + dict->window.pixels_so_far = 0; + + return TRUE; +} + +/* initializes an empty window (segs and encoder_heads should be pre allocated. + resets the image infos, and calls the free_image usr callback*/ +static void glz_dictionary_window_reset(SharedDictionary *dict) +{ + uint32_t i; + WindowImageSegment *seg, *last_seg; + + last_seg = dict->window.segs + dict->window.segs_quota; + /* reset free segs list */ + dict->window.free_segs_head = 0; + for (seg = dict->window.segs, i = 0; seg < last_seg; seg++, i++) { + seg->next = i + 1; + seg->image = NULL; + seg->lines = NULL; + seg->lines_end = NULL; + seg->pixels_num = 0; + seg->pixels_so_far = 0; + } + dict->window.segs[dict->window.segs_quota - 1].next = NULL_IMAGE_SEG_ID; + + dict->window.used_segs_head = NULL_IMAGE_SEG_ID; + dict->window.used_segs_tail = NULL_IMAGE_SEG_ID; + + // reset encoders heads + for (i = 0; i < dict->max_encoders; i++) { + dict->window.encoders_heads[i] = NULL_IMAGE_SEG_ID; + } + + __glz_dictionary_window_reset_images(dict); +} + +static inline void glz_dictionary_reset_hash(SharedDictionary *dict) +{ + memset(dict->htab, 0, sizeof(HashEntry) * HASH_SIZE * HASH_CHAIN_SIZE); +#ifdef CHAINED_HASH + memset(dict->htab_counter, 0, HASH_SIZE * sizeof(uint8_t)); +#endif +} + +static inline void glz_dictionary_window_destroy(SharedDictionary *dict) +{ + __glz_dictionary_window_reset_images(dict); + + if (dict->window.segs) { + dict->cur_usr->free(dict->cur_usr, dict->window.segs); + dict->window.segs = NULL; + } + + while (dict->window.free_images) { + WindowImage *tmp = dict->window.free_images; + dict->window.free_images = tmp->next; + + dict->cur_usr->free(dict->cur_usr, tmp); + } + + if (dict->window.encoders_heads) { + dict->cur_usr->free(dict->cur_usr, dict->window.encoders_heads); + dict->window.encoders_heads = NULL; + } +} + +/* logic removal only */ +static inline void glz_dictionary_window_kill_image(SharedDictionary *dict, WindowImage *image) +{ + image->is_alive = FALSE; +} + +GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders, + GlzEncoderUsrContext *usr) +{ + SharedDictionary *dict; + + if (!(dict = (SharedDictionary *)usr->malloc(usr, + sizeof(SharedDictionary)))) { + return NULL; + } + + dict->cur_usr = usr; + dict->last_image_id = 0; + dict->max_encoders = max_encoders; + + pthread_mutex_init(&dict->lock, NULL); + pthread_rwlock_init(&dict->rw_alloc_lock, NULL); + + dict->window.encoders_heads = NULL; + + // alloc window fields and reset + if (!glz_dictionary_window_create(dict, size)) { + dict->cur_usr->free(usr, dict); + return NULL; + } + + // reset window and hash + glz_enc_dictionary_reset((GlzEncDictContext *)dict, usr); + + return (GlzEncDictContext *)dict; +} + +void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict, + GlzEncDictRestoreData *out_data, GlzEncoderUsrContext *usr) +{ + SharedDictionary *dict = (SharedDictionary *)opaque_dict; + dict->cur_usr = usr; + GLZ_ASSERT(dict->cur_usr, opaque_dict); + GLZ_ASSERT(dict->cur_usr, out_data); + + out_data->last_image_id = dict->last_image_id; + out_data->max_encoders = dict->max_encoders; + out_data->size = dict->window.size_limit; +} + +GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data, + GlzEncoderUsrContext *usr) +{ + if (!restore_data) { + return NULL; + } + SharedDictionary *ret = (SharedDictionary *)glz_enc_dictionary_create( + restore_data->size, restore_data->max_encoders, usr); + ret->last_image_id = restore_data->last_image_id; + return ((GlzEncDictContext *)ret); +} + +void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr) +{ + SharedDictionary *dict = (SharedDictionary *)opaque_dict; + dict->cur_usr = usr; + GLZ_ASSERT(dict->cur_usr, opaque_dict); + + dict->last_image_id = 0; + glz_dictionary_window_reset(dict); + glz_dictionary_reset_hash(dict); +} + +void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr) +{ + SharedDictionary *dict = (SharedDictionary *)opaque_dict; + + if (!opaque_dict) { + return; + } + + dict->cur_usr = usr; + glz_dictionary_window_destroy(dict); + + pthread_mutex_destroy(&dict->lock); + pthread_rwlock_destroy(&dict->rw_alloc_lock); + + dict->cur_usr->free(dict->cur_usr, dict); +} + +uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *opaque_dict) +{ + SharedDictionary *dict = (SharedDictionary *)opaque_dict; + + if (!opaque_dict) { + return 0; + } + return dict->window.size_limit; +} + +/* doesn't call the remove image callback */ +void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict, + GlzEncDictImageContext *opaque_image, + GlzEncoderUsrContext *usr) +{ + SharedDictionary *dict = (SharedDictionary *)opaque_dict; + WindowImage *image = (WindowImage *)opaque_image; + dict->cur_usr = usr; + GLZ_ASSERT(dict->cur_usr, opaque_image && opaque_dict); + + glz_dictionary_window_kill_image(dict, image); +} + +/*********************************************************************************** + Mutators of the window. Should be called by the encoder before and after encoding. + ***********************************************************************************/ + +static inline int __get_pixels_num(LzImageType image_type, unsigned int num_lines, int stride) +{ + if (IS_IMAGE_TYPE_RGB[image_type]) { + return num_lines * stride / RGB_BYTES_PER_PIXEL[image_type]; + } else { + return num_lines * stride * PLT_PIXELS_PER_BYTE[image_type]; + } +} + +static void __glz_dictionary_window_segs_realloc(SharedDictionary *dict) +{ + WindowImageSegment *new_segs; + uint32_t new_quota = (MAX_IMAGE_SEGS_NUM < (dict->window.segs_quota * 2)) ? + MAX_IMAGE_SEGS_NUM : (dict->window.segs_quota * 2); + WindowImageSegment *seg; + uint32_t i; + + pthread_rwlock_wrlock(&dict->rw_alloc_lock); + + if (dict->window.segs_quota == MAX_IMAGE_SEGS_NUM) { + dict->cur_usr->error(dict->cur_usr, "overflow in image segments window\n"); + } + + new_segs = (WindowImageSegment*)dict->cur_usr->malloc( + dict->cur_usr, sizeof(WindowImageSegment) * new_quota); + + if (!new_segs) { + dict->cur_usr->error(dict->cur_usr, + "realloc of dictionary window failed\n"); + } + + memcpy(new_segs, dict->window.segs, + sizeof(WindowImageSegment) * dict->window.segs_quota); + + // resetting the new elements + for (i = dict->window.segs_quota, seg = new_segs + i; i < new_quota; i++, seg++) { + seg->image = NULL; + seg->lines = NULL; + seg->lines_end = NULL; + seg->pixels_num = 0; + seg->pixels_so_far = 0; + seg->next = i + 1; + } + new_segs[new_quota - 1].next = dict->window.free_segs_head; + dict->window.free_segs_head = dict->window.segs_quota; + + dict->cur_usr->free(dict->cur_usr, dict->window.segs); + dict->window.segs = new_segs; + dict->window.segs_quota = new_quota; + + pthread_rwlock_unlock(&dict->rw_alloc_lock); +} + +/* NOTE - it also updates the used_images_list*/ +static WindowImage *__glz_dictionary_window_alloc_image(SharedDictionary *dict) +{ + WindowImage *ret; + + if (dict->window.free_images) { + ret = dict->window.free_images; + dict->window.free_images = ret->next; + } else { + if (!(ret = (WindowImage *)dict->cur_usr->malloc(dict->cur_usr, + sizeof(*ret)))) { + return NULL; + } + } + + ret->next = NULL; + if (dict->window.used_images_tail) { + dict->window.used_images_tail->next = ret; + } + dict->window.used_images_tail = ret; + + if (!dict->window.used_images_head) { + dict->window.used_images_head = ret; + } + return ret; +} + +/* NOTE - it doesn't update the used_segs list*/ +static uint32_t __glz_dictionary_window_alloc_image_seg(SharedDictionary *dict) +{ + uint32_t seg_id; + WindowImageSegment *seg; + + // TODO: when is it best to realloc? when full or when half full? + if (dict->window.free_segs_head == NULL_IMAGE_SEG_ID) { + __glz_dictionary_window_segs_realloc(dict); + } + + GLZ_ASSERT(dict->cur_usr, dict->window.free_segs_head != NULL_IMAGE_SEG_ID); + + seg_id = dict->window.free_segs_head; + seg = dict->window.segs + seg_id; + dict->window.free_segs_head = seg->next; + + return seg_id; +} + +/* moves image to free list and "kill" it. Calls the free_image callback if was alive. */ +static inline void __glz_dictionary_window_free_image(SharedDictionary *dict, WindowImage *image) +{ + if (image->is_alive) { + dict->cur_usr->free_image(dict->cur_usr, image->usr_context); + } + image->is_alive = FALSE; + image->next = dict->window.free_images; + dict->window.free_images = image; +} + +/* moves all the segments that were associated with the images to the free segments */ +static inline void __glz_dictionary_window_free_image_segs(SharedDictionary *dict, + WindowImage *image) +{ + uint32_t old_free_head = dict->window.free_segs_head; + uint32_t seg_id, next_seg_id; + + GLZ_ASSERT(dict->cur_usr, image->first_seg != NULL_IMAGE_SEG_ID); + dict->window.free_segs_head = image->first_seg; + + // retrieving the last segment of the image + for (seg_id = image->first_seg, next_seg_id = dict->window.segs[seg_id].next; + (next_seg_id != NULL_IMAGE_SEG_ID) && (dict->window.segs[next_seg_id].image == image); + seg_id = next_seg_id, next_seg_id = dict->window.segs[seg_id].next) { + } + + // concatenate the free list + dict->window.segs[seg_id].next = old_free_head; +} + +/* Returns the logical head of the window after we add an image with the give size to its tail. + Returns NULL when the window is empty, of when we have to empty the window in order + to insert the new image. */ +static WindowImage *glz_dictionary_window_get_new_head(SharedDictionary *dict, int new_image_size) +{ + uint32_t cur_win_size; + WindowImage *cur_head; + + if ((uint32_t)new_image_size > dict->window.size_limit) { + dict->cur_usr->error(dict->cur_usr, "image is bigger than window\n"); + } + + GLZ_ASSERT(dict->cur_usr, new_image_size < dict->window.size_limit) + + // the window is empty + if (!dict->window.used_images_head) { + return NULL; + } + + GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_head != NULL_IMAGE_SEG_ID); + GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_tail != NULL_IMAGE_SEG_ID); + + // used_segs_head is the latest logical head (the physical head may preceed it) + cur_head = dict->window.segs[dict->window.used_segs_head].image; + cur_win_size = dict->window.segs[dict->window.used_segs_tail].pixels_num + + dict->window.segs[dict->window.used_segs_tail].pixels_so_far - + dict->window.segs[dict->window.used_segs_head].pixels_so_far; + + while ((cur_win_size + new_image_size) > dict->window.size_limit) { + GLZ_ASSERT(dict->cur_usr, cur_head); + cur_win_size -= cur_head->size; + cur_head = cur_head->next; + } + + return cur_head; +} + +static inline int glz_dictionary_is_in_use(SharedDictionary *dict) +{ + uint32_t i = 0; + for (i = 0; i < dict->max_encoders; i++) { + if (dict->window.encoders_heads[i] != NULL_IMAGE_SEG_ID) { + return TRUE; + } + } + return FALSE; +} + +/* remove from the window (and free relevant data) the images between the oldest physical head + (inclusive) and the end_image (exclusive). If end_image is NULL, empties the window*/ +static void glz_dictionary_window_remove_head(SharedDictionary *dict, uint32_t encoder_id, + WindowImage *end_image) +{ + // note that the segs list heads (one per encoder) may be different than the + // used_segs_head and it is updated somewhere else + while (dict->window.used_images_head != end_image) { + WindowImage *image = dict->window.used_images_head; + + __glz_dictionary_window_free_image_segs(dict, image); + dict->window.used_images_head = image->next; + __glz_dictionary_window_free_image(dict, image); + } + + if (!dict->window.used_images_head) { + dict->window.used_segs_head = NULL_IMAGE_SEG_ID; + dict->window.used_segs_tail = NULL_IMAGE_SEG_ID; + dict->window.used_images_tail = NULL; + } else { + dict->window.used_segs_head = end_image->first_seg; + } +} + +static uint32_t glz_dictionary_window_alloc_image_seg(SharedDictionary *dict, WindowImage* image, + int size, int stride, + uint8_t *lines, unsigned int num_lines) +{ + uint32_t seg_id = __glz_dictionary_window_alloc_image_seg(dict); + WindowImageSegment *seg = &dict->window.segs[seg_id]; + + seg->image = image; + seg->lines = lines; + seg->lines_end = lines + num_lines * stride; + seg->pixels_num = size; + seg->pixels_so_far = dict->window.pixels_so_far; + dict->window.pixels_so_far += seg->pixels_num; + + seg->next = NULL_IMAGE_SEG_ID; + + return seg_id; +} + +static WindowImage *glz_dictionary_window_add_image(SharedDictionary *dict, LzImageType image_type, + int image_size, int image_height, + int image_stride, uint8_t *first_lines, + unsigned int num_first_lines, + GlzUsrImageContext *usr_image_context) +{ + unsigned int num_lines = num_first_lines; + unsigned int row; + uint32_t seg_id, prev_seg_id; + uint8_t* lines = first_lines; + // alloc image info,update used head tail, if used_head null - update head + WindowImage *image = __glz_dictionary_window_alloc_image(dict); + image->id = dict->last_image_id++; + image->size = image_size; + image->type = image_type; + image->usr_context = usr_image_context; + + if (num_lines <= 0) { + num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines); + if (num_lines <= 0) { + dict->cur_usr->error(dict->cur_usr, "more lines failed\n"); + } + } + + for (row = 0;;) { + seg_id = glz_dictionary_window_alloc_image_seg(dict, image, + image_size * num_lines / image_height, + image_stride, + lines, num_lines); + if (row == 0) { + image->first_seg = seg_id; + } else { + dict->window.segs[prev_seg_id].next = seg_id; + } + + row += num_lines; + if (row < (uint32_t)image_height) { + num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines); + if (num_lines <= 0) { + dict->cur_usr->error(dict->cur_usr, "more lines failed\n"); + } + } else { + break; + } + prev_seg_id = seg_id; + } + + if (dict->window.used_segs_tail == NULL_IMAGE_SEG_ID) { + dict->window.used_segs_head = image->first_seg; + dict->window.used_segs_tail = seg_id; + } else { + int prev_tail = dict->window.used_segs_tail; + + // The used segs may be in use by another thread which is during encoding + // (read-only use - when going over the segs of an image, + // see glz_encode_tmpl::compress). + // Thus, the 'next' field of the list's tail can be accessed only + // after all the new tail's data was set. Note that we are relying on + // an atomic assignment (32 bit variable). + // For the other thread that may read 'next' of the old tail, NULL_IMAGE_SEG_ID + // is equivalent to a segment with an image id that is different + // from the image id of the tail, so we don't need to further protect this field. + dict->window.segs[prev_tail].next = image->first_seg; + dict->window.used_segs_tail = seg_id; + } + image->is_alive = TRUE; + + return image; +} + +WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr, + SharedDictionary *dict, LzImageType image_type, + int image_width, int image_height, int image_stride, + uint8_t *first_lines, unsigned int num_first_lines, + GlzUsrImageContext *usr_image_context, + uint32_t *image_head_dist) +{ + WindowImage *new_win_head, *ret; + int image_size; + + + pthread_mutex_lock(&dict->lock); + + dict->cur_usr = usr; + GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] == NULL_IMAGE_SEG_ID); + + image_size = __get_pixels_num(image_type, image_height, image_stride); + new_win_head = glz_dictionary_window_get_new_head(dict, image_size); + + if (!glz_dictionary_is_in_use(dict)) { + glz_dictionary_window_remove_head(dict, encoder_id, new_win_head); + } + + ret = glz_dictionary_window_add_image(dict, image_type, image_size, image_height, image_stride, + first_lines, num_first_lines, usr_image_context); + + if (new_win_head) { + dict->window.encoders_heads[encoder_id] = new_win_head->first_seg; + *image_head_dist = (uint32_t)(ret->id - new_win_head->id); // shouldn't be greater than 32 + // bit because the window size is + // limited to 2^25 + } else { + dict->window.encoders_heads[encoder_id] = ret->first_seg; + *image_head_dist = 0; + } + + + // update encoders head (the other heads were already updated) + pthread_mutex_unlock(&dict->lock); + pthread_rwlock_rdlock(&dict->rw_alloc_lock); + return ret; +} + +void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr, + SharedDictionary *dict) +{ + uint32_t i; + uint32_t early_head_seg = NULL_IMAGE_SEG_ID; + uint32_t this_encoder_head_seg; + + pthread_rwlock_unlock(&dict->rw_alloc_lock); + pthread_mutex_lock(&dict->lock); + dict->cur_usr = usr; + + GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] != NULL_IMAGE_SEG_ID); + // get the earliest head in use (not including this encoder head) + for (i = 0; i < dict->max_encoders; i++) { + if (i != encoder_id) { + if (IMAGE_SEG_IS_EARLIER(dict, dict->window.encoders_heads[i], early_head_seg)) { + early_head_seg = dict->window.encoders_heads[i]; + } + } + } + + // possible only if early_head_seg == NULL + if (IMAGE_SEG_IS_EARLIER(dict, dict->window.used_segs_head, early_head_seg)) { + early_head_seg = dict->window.used_segs_head; + } + + this_encoder_head_seg = dict->window.encoders_heads[encoder_id]; + + GLZ_ASSERT(dict->cur_usr, early_head_seg != NULL_IMAGE_SEG_ID); + + if (IMAGE_SEG_IS_EARLIER(dict, this_encoder_head_seg, early_head_seg)) { + GLZ_ASSERT(dict->cur_usr, + this_encoder_head_seg == dict->window.used_images_head->first_seg); + glz_dictionary_window_remove_head(dict, encoder_id, + dict->window.segs[early_head_seg].image); + } + + + dict->window.encoders_heads[encoder_id] = NULL_IMAGE_SEG_ID; + pthread_mutex_unlock(&dict->lock); +} diff --git a/server/glz-encoder-dict.h b/server/glz-encoder-dict.h new file mode 100644 index 0000000..960f165 --- /dev/null +++ b/server/glz-encoder-dict.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef GLZ_ENCODER_DICT_H_ +#define GLZ_ENCODER_DICT_H_ + +#include <stdint.h> +#include "glz_encoder_config.h" + +/* + Interface for maintaining lz dictionary that is shared among several encoders. + The interface for accessing the dictionary for encoding purposes is located in + glz-encoder-priv.h +*/ + +typedef void GlzEncDictContext; +typedef void GlzEncDictImageContext; + +/* NOTE: DISPLAY_MIGRATE_DATA_VERSION should change in case GlzEncDictRestoreData changes*/ +typedef struct GlzEncDictRestoreData { + uint32_t size; + uint32_t max_encoders; + uint64_t last_image_id; +} GlzEncDictRestoreData; + +/* size : maximal number of pixels occupying the window + max_encoders: maximal number of encoders that use the dictionary + usr : callbacks */ +GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders, + GlzEncoderUsrContext *usr); + +void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr); + +/* returns the window capacity in pixels */ +uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *); + +/* returns the current state of the dictionary. + NOTE - you should use it only when no encoder uses the dictionary. */ +void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict, + GlzEncDictRestoreData *out_data, + GlzEncoderUsrContext *usr); + +/* creates a dictionary and initialized it by use the given info */ +GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data, + GlzEncoderUsrContext *usr); + +/* NOTE - you should use this routine only when no encoder uses the dictionary. */ +void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr); + +/* image: the context returned by the encoder when the image was encoded. + NOTE - you should use this routine only when no encoder uses the dictionary.*/ +void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict, + GlzEncDictImageContext *image, GlzEncoderUsrContext *usr); + +#endif // GLZ_ENCODER_DICT_H_ diff --git a/server/glz-encoder-priv.h b/server/glz-encoder-priv.h new file mode 100644 index 0000000..a408966 --- /dev/null +++ b/server/glz-encoder-priv.h @@ -0,0 +1,186 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef GLZ_ENCODER_PRIV_H_ +#define GLZ_ENCODER_PRIV_H_ + +/* Interface for using the dictionary for encoding. + Data structures are exposed for the encoder for efficiency + purposes. */ +typedef struct WindowImage WindowImage; +typedef struct WindowImageSegment WindowImageSegment; + + +//#define CHAINED_HASH + +#ifdef CHAINED_HASH +#define HASH_SIZE_LOG 16 +#define HASH_CHAIN_SIZE 4 +#else +#define HASH_SIZE_LOG 20 +#define HASH_CHAIN_SIZE 1 +#endif + +#define HASH_SIZE (1 << HASH_SIZE_LOG) +#define HASH_MASK (HASH_SIZE - 1) + +typedef struct HashEntry HashEntry; + +typedef struct SharedDictionary SharedDictionary; + +struct WindowImage { + uint64_t id; + LzImageType type; + int size; // in pixels + uint32_t first_seg; + GlzUsrImageContext *usr_context; + WindowImage* next; + uint8_t is_alive; +}; + +#define MAX_IMAGE_SEGS_NUM (0xffffffff) +#define NULL_IMAGE_SEG_ID MAX_IMAGE_SEGS_NUM +#define INIT_IMAGE_SEGS_NUM 1000 + +/* Images can be separated into several chunks. The basic unit of the + dictionary window is one image segment. Each segment is encoded separately. + An encoded match can refer to only one segment.*/ +struct WindowImageSegment { + WindowImage *image; + void *lines; + void *lines_end; + uint32_t pixels_num; // Number of pixels in the segment + uint64_t pixels_so_far; // Total no. pixels passed through the window till this segment. + // NOTE - never use size delta independently. It should + // always be used with respect to a previous size delta + uint32_t next; +}; + + +struct __attribute__ ((__packed__)) HashEntry { + uint32_t image_seg_idx; + uint32_t ref_pix_idx; +}; + + +struct SharedDictionary { + struct { + /* The segments storage. A dynamic array. + By referring to a segment by its index, instead of address, + we save space in the hash entries (32bit instead of 64bit) */ + WindowImageSegment *segs; + uint32_t segs_quota; + + /* The window is manged as a linked list rather than as a cyclic + array in order to keep the indices of the segments consistent + after reallocation */ + + /* the window in a resolution of image segments */ + uint32_t used_segs_head; // the latest head + uint32_t used_segs_tail; + uint32_t free_segs_head; + + uint32_t *encoders_heads; // Holds for each encoder (by id), the window head when + // it started the encoding. + // The head is NULL_IMAGE_SEG_ID when the encoder is + // not encoding. + + /* the window in a resolution of images. But here the head contains the oldest head*/ + WindowImage* used_images_tail; + WindowImage* used_images_head; + WindowImage* free_images; + + uint64_t pixels_so_far; + uint32_t size_limit; // max number of pixels in a window (per encoder) + } window; + + /* Concurrency issues: the reading/writing of each entry field should be atomic. + It is allowed that the reading/writing of the whole entry won't be atomic, + since before we access a reference we check its validity*/ +#ifdef CHAINED_HASH + HashEntry htab[HASH_SIZE][HASH_CHAIN_SIZE]; + uint8_t htab_counter[HASH_SIZE]; //cyclic counter for the next entry in a chain to be assigned +#else + HashEntry htab[HASH_SIZE]; +#endif + + uint64_t last_image_id; + uint32_t max_encoders; + pthread_mutex_t lock; + pthread_rwlock_t rw_alloc_lock; + GlzEncoderUsrContext *cur_usr; // each encoder has other context. +}; + +/* + Add the image to the tail of the window. + If possible, release images from the head of the window. + Also perform concurrency related operations. + + usr_image_context: when an image is released from the window due to capacity overflow, + usr_image_context is given as a parameter to the free_image callback. + + image_head_dist : the number of images between the current image and the head of the + window that is associated with the encoder. +*/ +WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr, + SharedDictionary *dict, LzImageType image_type, + int image_width, int image_height, int image_stride, + uint8_t *first_lines, unsigned int num_first_lines, + GlzUsrImageContext *usr_image_context, + uint32_t *image_head_dist); + +/* + Performs concurrency related operations. + If possible, release images from the head of the window. +*/ +void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr, + SharedDictionary *dict); + +#define IMAGE_SEG_IS_EARLIER(dict, dst_seg, src_seg) ( \ + ((src_seg) == NULL_IMAGE_SEG_ID) || (((dst_seg) != NULL_IMAGE_SEG_ID) \ + && ((dict)->window.segs[(dst_seg)].pixels_so_far < \ + (dict)->window.segs[(src_seg)].pixels_so_far))) + + +#ifdef CHAINED_HASH +#define UPDATE_HASH(dict, hval, seg, pix) { \ + uint8_t tmp_count = (dict)->htab_counter[hval]; \ + (dict)->htab[hval][tmp_count].image_seg_idx = seg; \ + (dict)->htab[hval][tmp_count].ref_pix_idx = pix; \ + tmp_count = ((tmp_count) + 1) & (HASH_CHAIN_SIZE - 1); \ + dict->htab_counter[hval] = tmp_count; \ +} +#else +#define UPDATE_HASH(dict, hval, seg, pix) { \ + (dict)->htab[hval].image_seg_idx = seg; \ + (dict)->htab[hval].ref_pix_idx = pix; \ +} +#endif + +/* checks if the reference segment is located in the range of the window + of the current encoder */ +#define REF_SEG_IS_VALID(dict, enc_id, ref_seg, src_seg) ( \ + ((ref_seg) == (src_seg)) || \ + ((ref_seg)->image && \ + (ref_seg)->image->is_alive && \ + (src_seg->image->type == ref_seg->image->type) && \ + (ref_seg->pixels_so_far <= src_seg->pixels_so_far) && \ + ((dict)->window.segs[ \ + (dict)->window.encoders_heads[enc_id]].pixels_so_far <= \ + ref_seg->pixels_so_far))) + +#endif // GLZ_ENCODER_PRIV_H_ diff --git a/server/glz-encoder.c b/server/glz-encoder.c new file mode 100644 index 0000000..f761330 --- /dev/null +++ b/server/glz-encoder.c @@ -0,0 +1,311 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <pthread.h> +#include <stdio.h> +#include "glz-encoder.h" +#include "glz-encoder-priv.h" + + +/* Holds a specific data for one encoder, and data that is relevant for the current image encoded */ +typedef struct Encoder { + GlzEncoderUsrContext *usr; + uint8_t id; + SharedDictionary *dict; + + struct { + LzImageType type; + uint32_t id; + uint32_t first_win_seg; + } cur_image; + + struct { + uint8_t *start; + uint8_t *now; + uint8_t *end; + size_t bytes_count; + uint8_t *last_copy; // pointer to the last byte in which copy count was written + } io; +} Encoder; + + +/************************************************************************** +* Handling writing the encoded image to the output buffer +***************************************************************************/ +static inline int more_io_bytes(Encoder *encoder) +{ + uint8_t *io_ptr; + int num_io_bytes = encoder->usr->more_space(encoder->usr, &io_ptr); + encoder->io.bytes_count += num_io_bytes; + encoder->io.now = io_ptr; + encoder->io.end = encoder->io.now + num_io_bytes; + return num_io_bytes; +} + +static inline void encode(Encoder *encoder, uint8_t byte) +{ + if (encoder->io.now == encoder->io.end) { + if (more_io_bytes(encoder) <= 0) { + encoder->usr->error(encoder->usr, "%s: no more bytes\n", __FUNCTION__); + } + GLZ_ASSERT(encoder->usr, encoder->io.now); + } + + GLZ_ASSERT(encoder->usr, encoder->io.now < encoder->io.end); + *(encoder->io.now++) = byte; +} + +static inline void encode_32(Encoder *encoder, unsigned int word) +{ + encode(encoder, (uint8_t)(word >> 24)); + encode(encoder, (uint8_t)(word >> 16) & 0x0000ff); + encode(encoder, (uint8_t)(word >> 8) & 0x0000ff); + encode(encoder, (uint8_t)(word & 0x0000ff)); +} + +static inline void encode_64(Encoder *encoder, uint64_t word) +{ + encode_32(encoder, (uint32_t)(word >> 32)); + encode_32(encoder, (uint32_t)(word & 0xffffff)); +} + +static inline void encode_copy_count(Encoder *encoder, uint8_t copy_count) +{ + encode(encoder, copy_count); + encoder->io.last_copy = encoder->io.now - 1; // io_now cannot be the first byte of the buffer +} + +static inline void update_copy_count(Encoder *encoder, uint8_t copy_count) +{ + GLZ_ASSERT(encoder->usr, encoder->io.last_copy); + *(encoder->io.last_copy) = copy_count; +} + +// decrease the io ptr by 1 +static inline void compress_output_prev(Encoder *encoder) +{ + // io_now cannot be the first byte of the buffer + encoder->io.now--; + // the function should be called only when copy count is written unnecessarily by glz_compress + GLZ_ASSERT(encoder->usr, encoder->io.now == encoder->io.last_copy) +} + +static int encoder_reset(Encoder *encoder, uint8_t *io_ptr, uint8_t *io_ptr_end) +{ + GLZ_ASSERT(encoder->usr, io_ptr <= io_ptr_end); + encoder->io.bytes_count = io_ptr_end - io_ptr; + encoder->io.start = io_ptr; + encoder->io.now = io_ptr; + encoder->io.end = io_ptr_end; + encoder->io.last_copy = NULL; + + return TRUE; +} + +/********************************************************** +* Encoding +***********************************************************/ + +GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary, + GlzEncoderUsrContext *usr) +{ + Encoder *encoder; + + if (!usr || !usr->error || !usr->warn || !usr->info || !usr->malloc || + !usr->free || !usr->more_space) { + return NULL; + } + + if (!(encoder = (Encoder *)usr->malloc(usr, sizeof(Encoder)))) { + return NULL; + } + + encoder->id = id; + encoder->usr = usr; + encoder->dict = (SharedDictionary *)dictionary; + + return (GlzEncoderContext *)encoder; +} + +void glz_encoder_destroy(GlzEncoderContext *opaque_encoder) +{ + Encoder *encoder = (Encoder *)opaque_encoder; + + if (!opaque_encoder) { + return; + } + + encoder->usr->free(encoder->usr, encoder); +} + +/* + * Give hints to the compiler for branch prediction optimization. + */ +#if defined(__GNUC__) && (__GNUC__ > 2) +#define LZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1)) +#define LZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0)) +#else +#define LZ_EXPECT_CONDITIONAL(c) (c) +#define LZ_UNEXPECT_CONDITIONAL(c) (c) +#endif + + +typedef uint8_t BYTE; + +typedef struct __attribute__ ((__packed__)) one_byte_pixel_t { + BYTE a; +} one_byte_pixel_t; + +typedef struct __attribute__ ((__packed__)) rgb32_pixel_t { + BYTE b; + BYTE g; + BYTE r; + BYTE pad; +} rgb32_pixel_t; + +typedef struct __attribute__ ((__packed__)) rgb24_pixel_t { + BYTE b; + BYTE g; + BYTE r; +} rgb24_pixel_t; + +typedef uint16_t rgb16_pixel_t; + +#define BOUND_OFFSET 2 +#define LIMIT_OFFSET 6 +#define MIN_FILE_SIZE 4 + +#define MAX_PIXEL_SHORT_DISTANCE 4096 // (1 << 12) +#define MAX_PIXEL_MEDIUM_DISTANCE 131072 // (1 << 17) 2 ^ (12 + 5) +#define MAX_PIXEL_LONG_DISTANCE 33554432 // (1 << 25) 2 ^ (12 + 5 + 8) +#define MAX_IMAGE_DIST 16777215 // (1 << 24 - 1) + + +//#define DEBUG_ENCODE + + +#define GLZ_ENCODE_SIZE +#include "glz-encode-match.tmpl.c" +#define GLZ_ENCODE_MATCH +#include "glz-encode-match.tmpl.c" + +#define LZ_PLT +#include "glz-encode.tmpl.c" + +#define LZ_RGB16 +#include "glz-encode.tmpl.c" + +#define LZ_RGB24 +#include "glz-encode.tmpl.c" + +#define LZ_RGB32 +#include "glz-encode.tmpl.c" + +#define LZ_RGB_ALPHA +#include "glz-encode.tmpl.c" + + +int glz_encode(GlzEncoderContext *opaque_encoder, + LzImageType type, int width, int height, int top_down, + uint8_t *lines, unsigned int num_lines, int stride, + uint8_t *io_ptr, unsigned int num_io_bytes, + GlzUsrImageContext *usr_context, GlzEncDictImageContext **o_enc_dict_context) +{ + Encoder *encoder = (Encoder *)opaque_encoder; + WindowImage *dict_image; + uint8_t *io_ptr_end = io_ptr + num_io_bytes; + uint32_t win_head_image_dist; + + if (IS_IMAGE_TYPE_PLT[type]) { + if (stride > (width / PLT_PIXELS_PER_BYTE[type])) { + if (((width % PLT_PIXELS_PER_BYTE[type]) == 0) || ( + (stride - (width / PLT_PIXELS_PER_BYTE[type])) > 1)) { + encoder->usr->error(encoder->usr, "stride overflows (plt)\n"); + } + } + } else { + if (stride != width * RGB_BYTES_PER_PIXEL[type]) { + encoder->usr->error(encoder->usr, "stride != width*bytes_per_pixel (rgb)\n"); + } + } + + // assign the output buffer + if (!encoder_reset(encoder, io_ptr, io_ptr_end)) { + encoder->usr->error(encoder->usr, "lz encoder io reset failed\n"); + } + + // first read the list of the image segments into the dictionary window + dict_image = glz_dictionary_pre_encode(encoder->id, encoder->usr, + encoder->dict, type, width, height, stride, + lines, num_lines, usr_context, &win_head_image_dist); + *o_enc_dict_context = (GlzEncDictImageContext *)dict_image; + + encoder->cur_image.type = type; + encoder->cur_image.id = dict_image->id; + encoder->cur_image.first_win_seg = dict_image->first_seg; + + encode_32(encoder, GUINT32_TO_LE(LZ_MAGIC)); + encode_32(encoder, LZ_VERSION); + if (top_down) { + encode(encoder, (type & LZ_IMAGE_TYPE_MASK) | (1 << LZ_IMAGE_TYPE_LOG)); + } else { + encode(encoder, (type & LZ_IMAGE_TYPE_MASK)); + } + + encode_32(encoder, width); + encode_32(encoder, height); + encode_32(encoder, stride); + encode_64(encoder, dict_image->id); + encode_32(encoder, win_head_image_dist); + + switch (encoder->cur_image.type) { + case LZ_IMAGE_TYPE_PLT1_BE: + case LZ_IMAGE_TYPE_PLT1_LE: + case LZ_IMAGE_TYPE_PLT4_BE: + case LZ_IMAGE_TYPE_PLT4_LE: + case LZ_IMAGE_TYPE_PLT8: + glz_plt_compress(encoder); + break; + case LZ_IMAGE_TYPE_RGB16: + glz_rgb16_compress(encoder); + break; + case LZ_IMAGE_TYPE_RGB24: + glz_rgb24_compress(encoder); + break; + case LZ_IMAGE_TYPE_RGB32: + glz_rgb32_compress(encoder); + break; + case LZ_IMAGE_TYPE_RGBA: + glz_rgb32_compress(encoder); + glz_rgb_alpha_compress(encoder); + break; + case LZ_IMAGE_TYPE_INVALID: + default: + encoder->usr->error(encoder->usr, "bad image type\n"); + } + + glz_dictionary_post_encode(encoder->id, encoder->usr, encoder->dict); + + // move all the used segments to the free ones + encoder->io.bytes_count -= (encoder->io.end - encoder->io.now); + + return encoder->io.bytes_count; +} diff --git a/server/glz-encoder.h b/server/glz-encoder.h new file mode 100644 index 0000000..93164ed --- /dev/null +++ b/server/glz-encoder.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _H_GLZ_ENCODER +#define _H_GLZ_ENCODER + +/* Manging the lz encoding using a dictionary that is shared among encoders */ + +#include <stdint.h> +#include "common/lz_common.h" +#include "glz-encoder-dict.h" +#include "glz_encoder_config.h" + +typedef void GlzEncoderContext; + +GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary, + GlzEncoderUsrContext *usr); + +void glz_encoder_destroy(GlzEncoderContext *opaque_encoder); + +/* + assumes width is in pixels and stride is in bytes + usr_context : when an image is released from the window due to capacity overflow, + usr_context is given as a parameter to the free_image callback. + o_enc_dict_context: if glz_enc_dictionary_remove_image is called, it should be + called with the o_enc_dict_context that is associated with + the image. + + return: the number of bytes in the compressed data and sets o_enc_dict_context + + NOTE : currently supports only rgb images in which width*bytes_per_pixel = stride OR + palette images in which stride equals the min number of bytes to hold a line. + The stride should be > 0 +*/ +int glz_encode(GlzEncoderContext *opaque_encoder, LzImageType type, int width, int height, + int top_down, uint8_t *lines, unsigned int num_lines, int stride, + uint8_t *io_ptr, unsigned int num_io_bytes, GlzUsrImageContext *usr_context, + GlzEncDictImageContext **o_enc_dict_context); + + +#endif // _H_GLZ_ENCODER diff --git a/server/glz_encoder.c b/server/glz_encoder.c deleted file mode 100644 index 65f4478..0000000 --- a/server/glz_encoder.c +++ /dev/null @@ -1,311 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <glib.h> -#include <pthread.h> -#include <stdio.h> -#include "glz_encoder.h" -#include "glz_encoder_dictionary_protected.h" - - -/* Holds a specific data for one encoder, and data that is relevant for the current image encoded */ -typedef struct Encoder { - GlzEncoderUsrContext *usr; - uint8_t id; - SharedDictionary *dict; - - struct { - LzImageType type; - uint32_t id; - uint32_t first_win_seg; - } cur_image; - - struct { - uint8_t *start; - uint8_t *now; - uint8_t *end; - size_t bytes_count; - uint8_t *last_copy; // pointer to the last byte in which copy count was written - } io; -} Encoder; - - -/************************************************************************** -* Handling writing the encoded image to the output buffer -***************************************************************************/ -static inline int more_io_bytes(Encoder *encoder) -{ - uint8_t *io_ptr; - int num_io_bytes = encoder->usr->more_space(encoder->usr, &io_ptr); - encoder->io.bytes_count += num_io_bytes; - encoder->io.now = io_ptr; - encoder->io.end = encoder->io.now + num_io_bytes; - return num_io_bytes; -} - -static inline void encode(Encoder *encoder, uint8_t byte) -{ - if (encoder->io.now == encoder->io.end) { - if (more_io_bytes(encoder) <= 0) { - encoder->usr->error(encoder->usr, "%s: no more bytes\n", __FUNCTION__); - } - GLZ_ASSERT(encoder->usr, encoder->io.now); - } - - GLZ_ASSERT(encoder->usr, encoder->io.now < encoder->io.end); - *(encoder->io.now++) = byte; -} - -static inline void encode_32(Encoder *encoder, unsigned int word) -{ - encode(encoder, (uint8_t)(word >> 24)); - encode(encoder, (uint8_t)(word >> 16) & 0x0000ff); - encode(encoder, (uint8_t)(word >> 8) & 0x0000ff); - encode(encoder, (uint8_t)(word & 0x0000ff)); -} - -static inline void encode_64(Encoder *encoder, uint64_t word) -{ - encode_32(encoder, (uint32_t)(word >> 32)); - encode_32(encoder, (uint32_t)(word & 0xffffff)); -} - -static inline void encode_copy_count(Encoder *encoder, uint8_t copy_count) -{ - encode(encoder, copy_count); - encoder->io.last_copy = encoder->io.now - 1; // io_now cannot be the first byte of the buffer -} - -static inline void update_copy_count(Encoder *encoder, uint8_t copy_count) -{ - GLZ_ASSERT(encoder->usr, encoder->io.last_copy); - *(encoder->io.last_copy) = copy_count; -} - -// decrease the io ptr by 1 -static inline void compress_output_prev(Encoder *encoder) -{ - // io_now cannot be the first byte of the buffer - encoder->io.now--; - // the function should be called only when copy count is written unnecessarily by glz_compress - GLZ_ASSERT(encoder->usr, encoder->io.now == encoder->io.last_copy) -} - -static int encoder_reset(Encoder *encoder, uint8_t *io_ptr, uint8_t *io_ptr_end) -{ - GLZ_ASSERT(encoder->usr, io_ptr <= io_ptr_end); - encoder->io.bytes_count = io_ptr_end - io_ptr; - encoder->io.start = io_ptr; - encoder->io.now = io_ptr; - encoder->io.end = io_ptr_end; - encoder->io.last_copy = NULL; - - return TRUE; -} - -/********************************************************** -* Encoding -***********************************************************/ - -GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary, - GlzEncoderUsrContext *usr) -{ - Encoder *encoder; - - if (!usr || !usr->error || !usr->warn || !usr->info || !usr->malloc || - !usr->free || !usr->more_space) { - return NULL; - } - - if (!(encoder = (Encoder *)usr->malloc(usr, sizeof(Encoder)))) { - return NULL; - } - - encoder->id = id; - encoder->usr = usr; - encoder->dict = (SharedDictionary *)dictionary; - - return (GlzEncoderContext *)encoder; -} - -void glz_encoder_destroy(GlzEncoderContext *opaque_encoder) -{ - Encoder *encoder = (Encoder *)opaque_encoder; - - if (!opaque_encoder) { - return; - } - - encoder->usr->free(encoder->usr, encoder); -} - -/* - * Give hints to the compiler for branch prediction optimization. - */ -#if defined(__GNUC__) && (__GNUC__ > 2) -#define LZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1)) -#define LZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0)) -#else -#define LZ_EXPECT_CONDITIONAL(c) (c) -#define LZ_UNEXPECT_CONDITIONAL(c) (c) -#endif - - -typedef uint8_t BYTE; - -typedef struct __attribute__ ((__packed__)) one_byte_pixel_t { - BYTE a; -} one_byte_pixel_t; - -typedef struct __attribute__ ((__packed__)) rgb32_pixel_t { - BYTE b; - BYTE g; - BYTE r; - BYTE pad; -} rgb32_pixel_t; - -typedef struct __attribute__ ((__packed__)) rgb24_pixel_t { - BYTE b; - BYTE g; - BYTE r; -} rgb24_pixel_t; - -typedef uint16_t rgb16_pixel_t; - -#define BOUND_OFFSET 2 -#define LIMIT_OFFSET 6 -#define MIN_FILE_SIZE 4 - -#define MAX_PIXEL_SHORT_DISTANCE 4096 // (1 << 12) -#define MAX_PIXEL_MEDIUM_DISTANCE 131072 // (1 << 17) 2 ^ (12 + 5) -#define MAX_PIXEL_LONG_DISTANCE 33554432 // (1 << 25) 2 ^ (12 + 5 + 8) -#define MAX_IMAGE_DIST 16777215 // (1 << 24 - 1) - - -//#define DEBUG_ENCODE - - -#define GLZ_ENCODE_SIZE -#include "glz-encode-match.tmpl.c" -#define GLZ_ENCODE_MATCH -#include "glz-encode-match.tmpl.c" - -#define LZ_PLT -#include "glz-encode.tmpl.c" - -#define LZ_RGB16 -#include "glz-encode.tmpl.c" - -#define LZ_RGB24 -#include "glz-encode.tmpl.c" - -#define LZ_RGB32 -#include "glz-encode.tmpl.c" - -#define LZ_RGB_ALPHA -#include "glz-encode.tmpl.c" - - -int glz_encode(GlzEncoderContext *opaque_encoder, - LzImageType type, int width, int height, int top_down, - uint8_t *lines, unsigned int num_lines, int stride, - uint8_t *io_ptr, unsigned int num_io_bytes, - GlzUsrImageContext *usr_context, GlzEncDictImageContext **o_enc_dict_context) -{ - Encoder *encoder = (Encoder *)opaque_encoder; - WindowImage *dict_image; - uint8_t *io_ptr_end = io_ptr + num_io_bytes; - uint32_t win_head_image_dist; - - if (IS_IMAGE_TYPE_PLT[type]) { - if (stride > (width / PLT_PIXELS_PER_BYTE[type])) { - if (((width % PLT_PIXELS_PER_BYTE[type]) == 0) || ( - (stride - (width / PLT_PIXELS_PER_BYTE[type])) > 1)) { - encoder->usr->error(encoder->usr, "stride overflows (plt)\n"); - } - } - } else { - if (stride != width * RGB_BYTES_PER_PIXEL[type]) { - encoder->usr->error(encoder->usr, "stride != width*bytes_per_pixel (rgb)\n"); - } - } - - // assign the output buffer - if (!encoder_reset(encoder, io_ptr, io_ptr_end)) { - encoder->usr->error(encoder->usr, "lz encoder io reset failed\n"); - } - - // first read the list of the image segments into the dictionary window - dict_image = glz_dictionary_pre_encode(encoder->id, encoder->usr, - encoder->dict, type, width, height, stride, - lines, num_lines, usr_context, &win_head_image_dist); - *o_enc_dict_context = (GlzEncDictImageContext *)dict_image; - - encoder->cur_image.type = type; - encoder->cur_image.id = dict_image->id; - encoder->cur_image.first_win_seg = dict_image->first_seg; - - encode_32(encoder, GUINT32_TO_LE(LZ_MAGIC)); - encode_32(encoder, LZ_VERSION); - if (top_down) { - encode(encoder, (type & LZ_IMAGE_TYPE_MASK) | (1 << LZ_IMAGE_TYPE_LOG)); - } else { - encode(encoder, (type & LZ_IMAGE_TYPE_MASK)); - } - - encode_32(encoder, width); - encode_32(encoder, height); - encode_32(encoder, stride); - encode_64(encoder, dict_image->id); - encode_32(encoder, win_head_image_dist); - - switch (encoder->cur_image.type) { - case LZ_IMAGE_TYPE_PLT1_BE: - case LZ_IMAGE_TYPE_PLT1_LE: - case LZ_IMAGE_TYPE_PLT4_BE: - case LZ_IMAGE_TYPE_PLT4_LE: - case LZ_IMAGE_TYPE_PLT8: - glz_plt_compress(encoder); - break; - case LZ_IMAGE_TYPE_RGB16: - glz_rgb16_compress(encoder); - break; - case LZ_IMAGE_TYPE_RGB24: - glz_rgb24_compress(encoder); - break; - case LZ_IMAGE_TYPE_RGB32: - glz_rgb32_compress(encoder); - break; - case LZ_IMAGE_TYPE_RGBA: - glz_rgb32_compress(encoder); - glz_rgb_alpha_compress(encoder); - break; - case LZ_IMAGE_TYPE_INVALID: - default: - encoder->usr->error(encoder->usr, "bad image type\n"); - } - - glz_dictionary_post_encode(encoder->id, encoder->usr, encoder->dict); - - // move all the used segments to the free ones - encoder->io.bytes_count -= (encoder->io.end - encoder->io.now); - - return encoder->io.bytes_count; -} diff --git a/server/glz_encoder.h b/server/glz_encoder.h deleted file mode 100644 index e91f515..0000000 --- a/server/glz_encoder.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef _H_GLZ_ENCODER -#define _H_GLZ_ENCODER - -/* Manging the lz encoding using a dictionary that is shared among encoders */ - -#include <stdint.h> -#include "common/lz_common.h" -#include "glz_encoder_dictionary.h" -#include "glz_encoder_config.h" - -typedef void GlzEncoderContext; - -GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary, - GlzEncoderUsrContext *usr); - -void glz_encoder_destroy(GlzEncoderContext *opaque_encoder); - -/* - assumes width is in pixels and stride is in bytes - usr_context : when an image is released from the window due to capacity overflow, - usr_context is given as a parameter to the free_image callback. - o_enc_dict_context: if glz_enc_dictionary_remove_image is called, it should be - called with the o_enc_dict_context that is associated with - the image. - - return: the number of bytes in the compressed data and sets o_enc_dict_context - - NOTE : currently supports only rgb images in which width*bytes_per_pixel = stride OR - palette images in which stride equals the min number of bytes to hold a line. - The stride should be > 0 -*/ -int glz_encode(GlzEncoderContext *opaque_encoder, LzImageType type, int width, int height, - int top_down, uint8_t *lines, unsigned int num_lines, int stride, - uint8_t *io_ptr, unsigned int num_io_bytes, GlzUsrImageContext *usr_context, - GlzEncDictImageContext **o_enc_dict_context); - - -#endif // _H_GLZ_ENCODER diff --git a/server/glz_encoder_dictionary.c b/server/glz_encoder_dictionary.c deleted file mode 100644 index 70226e1..0000000 --- a/server/glz_encoder_dictionary.c +++ /dev/null @@ -1,633 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <pthread.h> -#include <string.h> -#include <stdio.h> - -#include "glz_encoder_dictionary.h" -#include "glz_encoder_dictionary_protected.h" - -/* turning all used images to free ones. If they are alive, calling the free_image callback for - each one */ -static inline void __glz_dictionary_window_reset_images(SharedDictionary *dict) -{ - WindowImage *tmp; - - while (dict->window.used_images_head) { - tmp = dict->window.used_images_head; - dict->window.used_images_head = dict->window.used_images_head->next; - if (tmp->is_alive) { - dict->cur_usr->free_image(dict->cur_usr, tmp->usr_context); - } - tmp->next = dict->window.free_images; - tmp->is_alive = FALSE; - dict->window.free_images = tmp; - } - dict->window.used_images_tail = NULL; -} - -/* allocate window fields (no reset)*/ -static int glz_dictionary_window_create(SharedDictionary *dict, uint32_t size) -{ - if (size > LZ_MAX_WINDOW_SIZE) { - return FALSE; - } - - dict->window.size_limit = size; - dict->window.segs = (WindowImageSegment *)( - dict->cur_usr->malloc(dict->cur_usr, sizeof(WindowImageSegment) * INIT_IMAGE_SEGS_NUM)); - - if (!dict->window.segs) { - return FALSE; - } - - dict->window.segs_quota = INIT_IMAGE_SEGS_NUM; - - dict->window.encoders_heads = (uint32_t *)dict->cur_usr->malloc(dict->cur_usr, - sizeof(uint32_t) * dict->max_encoders); - - if (!dict->window.encoders_heads) { - dict->cur_usr->free(dict->cur_usr, dict->window.segs); - return FALSE; - } - - dict->window.used_images_head = NULL; - dict->window.used_images_tail = NULL; - dict->window.free_images = NULL; - dict->window.pixels_so_far = 0; - - return TRUE; -} - -/* initializes an empty window (segs and encoder_heads should be pre allocated. - resets the image infos, and calls the free_image usr callback*/ -static void glz_dictionary_window_reset(SharedDictionary *dict) -{ - uint32_t i; - WindowImageSegment *seg, *last_seg; - - last_seg = dict->window.segs + dict->window.segs_quota; - /* reset free segs list */ - dict->window.free_segs_head = 0; - for (seg = dict->window.segs, i = 0; seg < last_seg; seg++, i++) { - seg->next = i + 1; - seg->image = NULL; - seg->lines = NULL; - seg->lines_end = NULL; - seg->pixels_num = 0; - seg->pixels_so_far = 0; - } - dict->window.segs[dict->window.segs_quota - 1].next = NULL_IMAGE_SEG_ID; - - dict->window.used_segs_head = NULL_IMAGE_SEG_ID; - dict->window.used_segs_tail = NULL_IMAGE_SEG_ID; - - // reset encoders heads - for (i = 0; i < dict->max_encoders; i++) { - dict->window.encoders_heads[i] = NULL_IMAGE_SEG_ID; - } - - __glz_dictionary_window_reset_images(dict); -} - -static inline void glz_dictionary_reset_hash(SharedDictionary *dict) -{ - memset(dict->htab, 0, sizeof(HashEntry) * HASH_SIZE * HASH_CHAIN_SIZE); -#ifdef CHAINED_HASH - memset(dict->htab_counter, 0, HASH_SIZE * sizeof(uint8_t)); -#endif -} - -static inline void glz_dictionary_window_destroy(SharedDictionary *dict) -{ - __glz_dictionary_window_reset_images(dict); - - if (dict->window.segs) { - dict->cur_usr->free(dict->cur_usr, dict->window.segs); - dict->window.segs = NULL; - } - - while (dict->window.free_images) { - WindowImage *tmp = dict->window.free_images; - dict->window.free_images = tmp->next; - - dict->cur_usr->free(dict->cur_usr, tmp); - } - - if (dict->window.encoders_heads) { - dict->cur_usr->free(dict->cur_usr, dict->window.encoders_heads); - dict->window.encoders_heads = NULL; - } -} - -/* logic removal only */ -static inline void glz_dictionary_window_kill_image(SharedDictionary *dict, WindowImage *image) -{ - image->is_alive = FALSE; -} - -GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders, - GlzEncoderUsrContext *usr) -{ - SharedDictionary *dict; - - if (!(dict = (SharedDictionary *)usr->malloc(usr, - sizeof(SharedDictionary)))) { - return NULL; - } - - dict->cur_usr = usr; - dict->last_image_id = 0; - dict->max_encoders = max_encoders; - - pthread_mutex_init(&dict->lock, NULL); - pthread_rwlock_init(&dict->rw_alloc_lock, NULL); - - dict->window.encoders_heads = NULL; - - // alloc window fields and reset - if (!glz_dictionary_window_create(dict, size)) { - dict->cur_usr->free(usr, dict); - return NULL; - } - - // reset window and hash - glz_enc_dictionary_reset((GlzEncDictContext *)dict, usr); - - return (GlzEncDictContext *)dict; -} - -void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict, - GlzEncDictRestoreData *out_data, GlzEncoderUsrContext *usr) -{ - SharedDictionary *dict = (SharedDictionary *)opaque_dict; - dict->cur_usr = usr; - GLZ_ASSERT(dict->cur_usr, opaque_dict); - GLZ_ASSERT(dict->cur_usr, out_data); - - out_data->last_image_id = dict->last_image_id; - out_data->max_encoders = dict->max_encoders; - out_data->size = dict->window.size_limit; -} - -GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data, - GlzEncoderUsrContext *usr) -{ - if (!restore_data) { - return NULL; - } - SharedDictionary *ret = (SharedDictionary *)glz_enc_dictionary_create( - restore_data->size, restore_data->max_encoders, usr); - ret->last_image_id = restore_data->last_image_id; - return ((GlzEncDictContext *)ret); -} - -void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr) -{ - SharedDictionary *dict = (SharedDictionary *)opaque_dict; - dict->cur_usr = usr; - GLZ_ASSERT(dict->cur_usr, opaque_dict); - - dict->last_image_id = 0; - glz_dictionary_window_reset(dict); - glz_dictionary_reset_hash(dict); -} - -void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr) -{ - SharedDictionary *dict = (SharedDictionary *)opaque_dict; - - if (!opaque_dict) { - return; - } - - dict->cur_usr = usr; - glz_dictionary_window_destroy(dict); - - pthread_mutex_destroy(&dict->lock); - pthread_rwlock_destroy(&dict->rw_alloc_lock); - - dict->cur_usr->free(dict->cur_usr, dict); -} - -uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *opaque_dict) -{ - SharedDictionary *dict = (SharedDictionary *)opaque_dict; - - if (!opaque_dict) { - return 0; - } - return dict->window.size_limit; -} - -/* doesn't call the remove image callback */ -void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict, - GlzEncDictImageContext *opaque_image, - GlzEncoderUsrContext *usr) -{ - SharedDictionary *dict = (SharedDictionary *)opaque_dict; - WindowImage *image = (WindowImage *)opaque_image; - dict->cur_usr = usr; - GLZ_ASSERT(dict->cur_usr, opaque_image && opaque_dict); - - glz_dictionary_window_kill_image(dict, image); -} - -/*********************************************************************************** - Mutators of the window. Should be called by the encoder before and after encoding. - ***********************************************************************************/ - -static inline int __get_pixels_num(LzImageType image_type, unsigned int num_lines, int stride) -{ - if (IS_IMAGE_TYPE_RGB[image_type]) { - return num_lines * stride / RGB_BYTES_PER_PIXEL[image_type]; - } else { - return num_lines * stride * PLT_PIXELS_PER_BYTE[image_type]; - } -} - -static void __glz_dictionary_window_segs_realloc(SharedDictionary *dict) -{ - WindowImageSegment *new_segs; - uint32_t new_quota = (MAX_IMAGE_SEGS_NUM < (dict->window.segs_quota * 2)) ? - MAX_IMAGE_SEGS_NUM : (dict->window.segs_quota * 2); - WindowImageSegment *seg; - uint32_t i; - - pthread_rwlock_wrlock(&dict->rw_alloc_lock); - - if (dict->window.segs_quota == MAX_IMAGE_SEGS_NUM) { - dict->cur_usr->error(dict->cur_usr, "overflow in image segments window\n"); - } - - new_segs = (WindowImageSegment*)dict->cur_usr->malloc( - dict->cur_usr, sizeof(WindowImageSegment) * new_quota); - - if (!new_segs) { - dict->cur_usr->error(dict->cur_usr, - "realloc of dictionary window failed\n"); - } - - memcpy(new_segs, dict->window.segs, - sizeof(WindowImageSegment) * dict->window.segs_quota); - - // resetting the new elements - for (i = dict->window.segs_quota, seg = new_segs + i; i < new_quota; i++, seg++) { - seg->image = NULL; - seg->lines = NULL; - seg->lines_end = NULL; - seg->pixels_num = 0; - seg->pixels_so_far = 0; - seg->next = i + 1; - } - new_segs[new_quota - 1].next = dict->window.free_segs_head; - dict->window.free_segs_head = dict->window.segs_quota; - - dict->cur_usr->free(dict->cur_usr, dict->window.segs); - dict->window.segs = new_segs; - dict->window.segs_quota = new_quota; - - pthread_rwlock_unlock(&dict->rw_alloc_lock); -} - -/* NOTE - it also updates the used_images_list*/ -static WindowImage *__glz_dictionary_window_alloc_image(SharedDictionary *dict) -{ - WindowImage *ret; - - if (dict->window.free_images) { - ret = dict->window.free_images; - dict->window.free_images = ret->next; - } else { - if (!(ret = (WindowImage *)dict->cur_usr->malloc(dict->cur_usr, - sizeof(*ret)))) { - return NULL; - } - } - - ret->next = NULL; - if (dict->window.used_images_tail) { - dict->window.used_images_tail->next = ret; - } - dict->window.used_images_tail = ret; - - if (!dict->window.used_images_head) { - dict->window.used_images_head = ret; - } - return ret; -} - -/* NOTE - it doesn't update the used_segs list*/ -static uint32_t __glz_dictionary_window_alloc_image_seg(SharedDictionary *dict) -{ - uint32_t seg_id; - WindowImageSegment *seg; - - // TODO: when is it best to realloc? when full or when half full? - if (dict->window.free_segs_head == NULL_IMAGE_SEG_ID) { - __glz_dictionary_window_segs_realloc(dict); - } - - GLZ_ASSERT(dict->cur_usr, dict->window.free_segs_head != NULL_IMAGE_SEG_ID); - - seg_id = dict->window.free_segs_head; - seg = dict->window.segs + seg_id; - dict->window.free_segs_head = seg->next; - - return seg_id; -} - -/* moves image to free list and "kill" it. Calls the free_image callback if was alive. */ -static inline void __glz_dictionary_window_free_image(SharedDictionary *dict, WindowImage *image) -{ - if (image->is_alive) { - dict->cur_usr->free_image(dict->cur_usr, image->usr_context); - } - image->is_alive = FALSE; - image->next = dict->window.free_images; - dict->window.free_images = image; -} - -/* moves all the segments that were associated with the images to the free segments */ -static inline void __glz_dictionary_window_free_image_segs(SharedDictionary *dict, - WindowImage *image) -{ - uint32_t old_free_head = dict->window.free_segs_head; - uint32_t seg_id, next_seg_id; - - GLZ_ASSERT(dict->cur_usr, image->first_seg != NULL_IMAGE_SEG_ID); - dict->window.free_segs_head = image->first_seg; - - // retrieving the last segment of the image - for (seg_id = image->first_seg, next_seg_id = dict->window.segs[seg_id].next; - (next_seg_id != NULL_IMAGE_SEG_ID) && (dict->window.segs[next_seg_id].image == image); - seg_id = next_seg_id, next_seg_id = dict->window.segs[seg_id].next) { - } - - // concatenate the free list - dict->window.segs[seg_id].next = old_free_head; -} - -/* Returns the logical head of the window after we add an image with the give size to its tail. - Returns NULL when the window is empty, of when we have to empty the window in order - to insert the new image. */ -static WindowImage *glz_dictionary_window_get_new_head(SharedDictionary *dict, int new_image_size) -{ - uint32_t cur_win_size; - WindowImage *cur_head; - - if ((uint32_t)new_image_size > dict->window.size_limit) { - dict->cur_usr->error(dict->cur_usr, "image is bigger than window\n"); - } - - GLZ_ASSERT(dict->cur_usr, new_image_size < dict->window.size_limit) - - // the window is empty - if (!dict->window.used_images_head) { - return NULL; - } - - GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_head != NULL_IMAGE_SEG_ID); - GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_tail != NULL_IMAGE_SEG_ID); - - // used_segs_head is the latest logical head (the physical head may preceed it) - cur_head = dict->window.segs[dict->window.used_segs_head].image; - cur_win_size = dict->window.segs[dict->window.used_segs_tail].pixels_num + - dict->window.segs[dict->window.used_segs_tail].pixels_so_far - - dict->window.segs[dict->window.used_segs_head].pixels_so_far; - - while ((cur_win_size + new_image_size) > dict->window.size_limit) { - GLZ_ASSERT(dict->cur_usr, cur_head); - cur_win_size -= cur_head->size; - cur_head = cur_head->next; - } - - return cur_head; -} - -static inline int glz_dictionary_is_in_use(SharedDictionary *dict) -{ - uint32_t i = 0; - for (i = 0; i < dict->max_encoders; i++) { - if (dict->window.encoders_heads[i] != NULL_IMAGE_SEG_ID) { - return TRUE; - } - } - return FALSE; -} - -/* remove from the window (and free relevant data) the images between the oldest physical head - (inclusive) and the end_image (exclusive). If end_image is NULL, empties the window*/ -static void glz_dictionary_window_remove_head(SharedDictionary *dict, uint32_t encoder_id, - WindowImage *end_image) -{ - // note that the segs list heads (one per encoder) may be different than the - // used_segs_head and it is updated somewhere else - while (dict->window.used_images_head != end_image) { - WindowImage *image = dict->window.used_images_head; - - __glz_dictionary_window_free_image_segs(dict, image); - dict->window.used_images_head = image->next; - __glz_dictionary_window_free_image(dict, image); - } - - if (!dict->window.used_images_head) { - dict->window.used_segs_head = NULL_IMAGE_SEG_ID; - dict->window.used_segs_tail = NULL_IMAGE_SEG_ID; - dict->window.used_images_tail = NULL; - } else { - dict->window.used_segs_head = end_image->first_seg; - } -} - -static uint32_t glz_dictionary_window_alloc_image_seg(SharedDictionary *dict, WindowImage* image, - int size, int stride, - uint8_t *lines, unsigned int num_lines) -{ - uint32_t seg_id = __glz_dictionary_window_alloc_image_seg(dict); - WindowImageSegment *seg = &dict->window.segs[seg_id]; - - seg->image = image; - seg->lines = lines; - seg->lines_end = lines + num_lines * stride; - seg->pixels_num = size; - seg->pixels_so_far = dict->window.pixels_so_far; - dict->window.pixels_so_far += seg->pixels_num; - - seg->next = NULL_IMAGE_SEG_ID; - - return seg_id; -} - -static WindowImage *glz_dictionary_window_add_image(SharedDictionary *dict, LzImageType image_type, - int image_size, int image_height, - int image_stride, uint8_t *first_lines, - unsigned int num_first_lines, - GlzUsrImageContext *usr_image_context) -{ - unsigned int num_lines = num_first_lines; - unsigned int row; - uint32_t seg_id, prev_seg_id; - uint8_t* lines = first_lines; - // alloc image info,update used head tail, if used_head null - update head - WindowImage *image = __glz_dictionary_window_alloc_image(dict); - image->id = dict->last_image_id++; - image->size = image_size; - image->type = image_type; - image->usr_context = usr_image_context; - - if (num_lines <= 0) { - num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines); - if (num_lines <= 0) { - dict->cur_usr->error(dict->cur_usr, "more lines failed\n"); - } - } - - for (row = 0;;) { - seg_id = glz_dictionary_window_alloc_image_seg(dict, image, - image_size * num_lines / image_height, - image_stride, - lines, num_lines); - if (row == 0) { - image->first_seg = seg_id; - } else { - dict->window.segs[prev_seg_id].next = seg_id; - } - - row += num_lines; - if (row < (uint32_t)image_height) { - num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines); - if (num_lines <= 0) { - dict->cur_usr->error(dict->cur_usr, "more lines failed\n"); - } - } else { - break; - } - prev_seg_id = seg_id; - } - - if (dict->window.used_segs_tail == NULL_IMAGE_SEG_ID) { - dict->window.used_segs_head = image->first_seg; - dict->window.used_segs_tail = seg_id; - } else { - int prev_tail = dict->window.used_segs_tail; - - // The used segs may be in use by another thread which is during encoding - // (read-only use - when going over the segs of an image, - // see glz_encode_tmpl::compress). - // Thus, the 'next' field of the list's tail can be accessed only - // after all the new tail's data was set. Note that we are relying on - // an atomic assignment (32 bit variable). - // For the other thread that may read 'next' of the old tail, NULL_IMAGE_SEG_ID - // is equivalent to a segment with an image id that is different - // from the image id of the tail, so we don't need to further protect this field. - dict->window.segs[prev_tail].next = image->first_seg; - dict->window.used_segs_tail = seg_id; - } - image->is_alive = TRUE; - - return image; -} - -WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr, - SharedDictionary *dict, LzImageType image_type, - int image_width, int image_height, int image_stride, - uint8_t *first_lines, unsigned int num_first_lines, - GlzUsrImageContext *usr_image_context, - uint32_t *image_head_dist) -{ - WindowImage *new_win_head, *ret; - int image_size; - - - pthread_mutex_lock(&dict->lock); - - dict->cur_usr = usr; - GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] == NULL_IMAGE_SEG_ID); - - image_size = __get_pixels_num(image_type, image_height, image_stride); - new_win_head = glz_dictionary_window_get_new_head(dict, image_size); - - if (!glz_dictionary_is_in_use(dict)) { - glz_dictionary_window_remove_head(dict, encoder_id, new_win_head); - } - - ret = glz_dictionary_window_add_image(dict, image_type, image_size, image_height, image_stride, - first_lines, num_first_lines, usr_image_context); - - if (new_win_head) { - dict->window.encoders_heads[encoder_id] = new_win_head->first_seg; - *image_head_dist = (uint32_t)(ret->id - new_win_head->id); // shouldn't be greater than 32 - // bit because the window size is - // limited to 2^25 - } else { - dict->window.encoders_heads[encoder_id] = ret->first_seg; - *image_head_dist = 0; - } - - - // update encoders head (the other heads were already updated) - pthread_mutex_unlock(&dict->lock); - pthread_rwlock_rdlock(&dict->rw_alloc_lock); - return ret; -} - -void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr, - SharedDictionary *dict) -{ - uint32_t i; - uint32_t early_head_seg = NULL_IMAGE_SEG_ID; - uint32_t this_encoder_head_seg; - - pthread_rwlock_unlock(&dict->rw_alloc_lock); - pthread_mutex_lock(&dict->lock); - dict->cur_usr = usr; - - GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] != NULL_IMAGE_SEG_ID); - // get the earliest head in use (not including this encoder head) - for (i = 0; i < dict->max_encoders; i++) { - if (i != encoder_id) { - if (IMAGE_SEG_IS_EARLIER(dict, dict->window.encoders_heads[i], early_head_seg)) { - early_head_seg = dict->window.encoders_heads[i]; - } - } - } - - // possible only if early_head_seg == NULL - if (IMAGE_SEG_IS_EARLIER(dict, dict->window.used_segs_head, early_head_seg)) { - early_head_seg = dict->window.used_segs_head; - } - - this_encoder_head_seg = dict->window.encoders_heads[encoder_id]; - - GLZ_ASSERT(dict->cur_usr, early_head_seg != NULL_IMAGE_SEG_ID); - - if (IMAGE_SEG_IS_EARLIER(dict, this_encoder_head_seg, early_head_seg)) { - GLZ_ASSERT(dict->cur_usr, - this_encoder_head_seg == dict->window.used_images_head->first_seg); - glz_dictionary_window_remove_head(dict, encoder_id, - dict->window.segs[early_head_seg].image); - } - - - dict->window.encoders_heads[encoder_id] = NULL_IMAGE_SEG_ID; - pthread_mutex_unlock(&dict->lock); -} diff --git a/server/glz_encoder_dictionary.h b/server/glz_encoder_dictionary.h deleted file mode 100644 index eb57aa5..0000000 --- a/server/glz_encoder_dictionary.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef _H_GLZ_ENCODER_DICTIONARY -#define _H_GLZ_ENCODER_DICTIONARY - -#include <stdint.h> -#include "glz_encoder_config.h" - -/* - Interface for maintaining lz dictionary that is shared among several encoders. - The interface for accessing the dictionary for encoding purposes is located in - glz_encoder_dictionary_protected.h -*/ - -typedef void GlzEncDictContext; -typedef void GlzEncDictImageContext; - -/* NOTE: DISPLAY_MIGRATE_DATA_VERSION should change in case GlzEncDictRestoreData changes*/ -typedef struct GlzEncDictRestoreData { - uint32_t size; - uint32_t max_encoders; - uint64_t last_image_id; -} GlzEncDictRestoreData; - -/* size : maximal number of pixels occupying the window - max_encoders: maximal number of encoders that use the dictionary - usr : callbacks */ -GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders, - GlzEncoderUsrContext *usr); - -void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr); - -/* returns the window capacity in pixels */ -uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *); - -/* returns the current state of the dictionary. - NOTE - you should use it only when no encoder uses the dictionary. */ -void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict, - GlzEncDictRestoreData *out_data, - GlzEncoderUsrContext *usr); - -/* creates a dictionary and initialized it by use the given info */ -GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data, - GlzEncoderUsrContext *usr); - -/* NOTE - you should use this routine only when no encoder uses the dictionary. */ -void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr); - -/* image: the context returned by the encoder when the image was encoded. - NOTE - you should use this routine only when no encoder uses the dictionary.*/ -void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict, - GlzEncDictImageContext *image, GlzEncoderUsrContext *usr); - -#endif // _H_GLZ_ENCODER_DICTIONARY diff --git a/server/glz_encoder_dictionary_protected.h b/server/glz_encoder_dictionary_protected.h deleted file mode 100644 index 098684f..0000000 --- a/server/glz_encoder_dictionary_protected.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef _H_GLZ_ENCODER_DICTIONARY_PROTECTED -#define _H_GLZ_ENCODER_DICTIONARY_PROTECTED - -/* Interface for using the dictionary for encoding. - Data structures are exposed for the encoder for efficiency - purposes. */ -typedef struct WindowImage WindowImage; -typedef struct WindowImageSegment WindowImageSegment; - - -//#define CHAINED_HASH - -#ifdef CHAINED_HASH -#define HASH_SIZE_LOG 16 -#define HASH_CHAIN_SIZE 4 -#else -#define HASH_SIZE_LOG 20 -#define HASH_CHAIN_SIZE 1 -#endif - -#define HASH_SIZE (1 << HASH_SIZE_LOG) -#define HASH_MASK (HASH_SIZE - 1) - -typedef struct HashEntry HashEntry; - -typedef struct SharedDictionary SharedDictionary; - -struct WindowImage { - uint64_t id; - LzImageType type; - int size; // in pixels - uint32_t first_seg; - GlzUsrImageContext *usr_context; - WindowImage* next; - uint8_t is_alive; -}; - -#define MAX_IMAGE_SEGS_NUM (0xffffffff) -#define NULL_IMAGE_SEG_ID MAX_IMAGE_SEGS_NUM -#define INIT_IMAGE_SEGS_NUM 1000 - -/* Images can be separated into several chunks. The basic unit of the - dictionary window is one image segment. Each segment is encoded separately. - An encoded match can refer to only one segment.*/ -struct WindowImageSegment { - WindowImage *image; - void *lines; - void *lines_end; - uint32_t pixels_num; // Number of pixels in the segment - uint64_t pixels_so_far; // Total no. pixels passed through the window till this segment. - // NOTE - never use size delta independently. It should - // always be used with respect to a previous size delta - uint32_t next; -}; - - -struct __attribute__ ((__packed__)) HashEntry { - uint32_t image_seg_idx; - uint32_t ref_pix_idx; -}; - - -struct SharedDictionary { - struct { - /* The segments storage. A dynamic array. - By referring to a segment by its index, instead of address, - we save space in the hash entries (32bit instead of 64bit) */ - WindowImageSegment *segs; - uint32_t segs_quota; - - /* The window is manged as a linked list rather than as a cyclic - array in order to keep the indices of the segments consistent - after reallocation */ - - /* the window in a resolution of image segments */ - uint32_t used_segs_head; // the latest head - uint32_t used_segs_tail; - uint32_t free_segs_head; - - uint32_t *encoders_heads; // Holds for each encoder (by id), the window head when - // it started the encoding. - // The head is NULL_IMAGE_SEG_ID when the encoder is - // not encoding. - - /* the window in a resolution of images. But here the head contains the oldest head*/ - WindowImage* used_images_tail; - WindowImage* used_images_head; - WindowImage* free_images; - - uint64_t pixels_so_far; - uint32_t size_limit; // max number of pixels in a window (per encoder) - } window; - - /* Concurrency issues: the reading/writing of each entry field should be atomic. - It is allowed that the reading/writing of the whole entry won't be atomic, - since before we access a reference we check its validity*/ -#ifdef CHAINED_HASH - HashEntry htab[HASH_SIZE][HASH_CHAIN_SIZE]; - uint8_t htab_counter[HASH_SIZE]; //cyclic counter for the next entry in a chain to be assigned -#else - HashEntry htab[HASH_SIZE]; -#endif - - uint64_t last_image_id; - uint32_t max_encoders; - pthread_mutex_t lock; - pthread_rwlock_t rw_alloc_lock; - GlzEncoderUsrContext *cur_usr; // each encoder has other context. -}; - -/* - Add the image to the tail of the window. - If possible, release images from the head of the window. - Also perform concurrency related operations. - - usr_image_context: when an image is released from the window due to capacity overflow, - usr_image_context is given as a parameter to the free_image callback. - - image_head_dist : the number of images between the current image and the head of the - window that is associated with the encoder. -*/ -WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr, - SharedDictionary *dict, LzImageType image_type, - int image_width, int image_height, int image_stride, - uint8_t *first_lines, unsigned int num_first_lines, - GlzUsrImageContext *usr_image_context, - uint32_t *image_head_dist); - -/* - Performs concurrency related operations. - If possible, release images from the head of the window. -*/ -void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr, - SharedDictionary *dict); - -#define IMAGE_SEG_IS_EARLIER(dict, dst_seg, src_seg) ( \ - ((src_seg) == NULL_IMAGE_SEG_ID) || (((dst_seg) != NULL_IMAGE_SEG_ID) \ - && ((dict)->window.segs[(dst_seg)].pixels_so_far < \ - (dict)->window.segs[(src_seg)].pixels_so_far))) - - -#ifdef CHAINED_HASH -#define UPDATE_HASH(dict, hval, seg, pix) { \ - uint8_t tmp_count = (dict)->htab_counter[hval]; \ - (dict)->htab[hval][tmp_count].image_seg_idx = seg; \ - (dict)->htab[hval][tmp_count].ref_pix_idx = pix; \ - tmp_count = ((tmp_count) + 1) & (HASH_CHAIN_SIZE - 1); \ - dict->htab_counter[hval] = tmp_count; \ -} -#else -#define UPDATE_HASH(dict, hval, seg, pix) { \ - (dict)->htab[hval].image_seg_idx = seg; \ - (dict)->htab[hval].ref_pix_idx = pix; \ -} -#endif - -/* checks if the reference segment is located in the range of the window - of the current encoder */ -#define REF_SEG_IS_VALID(dict, enc_id, ref_seg, src_seg) ( \ - ((ref_seg) == (src_seg)) || \ - ((ref_seg)->image && \ - (ref_seg)->image->is_alive && \ - (src_seg->image->type == ref_seg->image->type) && \ - (ref_seg->pixels_so_far <= src_seg->pixels_so_far) && \ - ((dict)->window.segs[ \ - (dict)->window.encoders_heads[enc_id]].pixels_so_far <= \ - ref_seg->pixels_so_far))) - -#endif // _H_GLZ_ENCODER_DICTIONARY_PROTECTED diff --git a/server/image-cache.c b/server/image-cache.c new file mode 100644 index 0000000..f4d2ee9 --- /dev/null +++ b/server/image-cache.c @@ -0,0 +1,214 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include "image-cache.h" +#include "red_parse_qxl.h" +#include "display-channel.h" + +static ImageCacheItem *image_cache_find(ImageCache *cache, uint64_t id) +{ + ImageCacheItem *item = cache->hash_table[id % IMAGE_CACHE_HASH_SIZE]; + + while (item) { + if (item->id == id) { + return item; + } + item = item->next; + } + return NULL; +} + +int image_cache_hit(ImageCache *cache, uint64_t id) +{ + ImageCacheItem *item; + if (!(item = image_cache_find(cache, id))) { + return FALSE; + } +#ifdef IMAGE_CACHE_AGE + item->age = cache->age; +#endif + ring_remove(&item->lru_link); + ring_add(&cache->lru, &item->lru_link); + return TRUE; +} + +static void image_cache_remove(ImageCache *cache, ImageCacheItem *item) +{ + ImageCacheItem **now; + + now = &cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE]; + for (;;) { + spice_assert(*now); + if (*now == item) { + *now = item->next; + break; + } + now = &(*now)->next; + } + ring_remove(&item->lru_link); + pixman_image_unref(item->image); + free(item); +#ifndef IMAGE_CACHE_AGE + cache->num_items--; +#endif +} + +#define IMAGE_CACHE_MAX_ITEMS 2 + +static void image_cache_put(SpiceImageCache *spice_cache, uint64_t id, pixman_image_t *image) +{ + ImageCache *cache = (ImageCache *)spice_cache; + ImageCacheItem *item; + +#ifndef IMAGE_CACHE_AGE + if (cache->num_items == IMAGE_CACHE_MAX_ITEMS) { + ImageCacheItem *tail = (ImageCacheItem *)ring_get_tail(&cache->lru); + spice_assert(tail); + image_cache_remove(cache, tail); + } +#endif + + item = spice_new(ImageCacheItem, 1); + item->id = id; +#ifdef IMAGE_CACHE_AGE + item->age = cache->age; +#else + cache->num_items++; +#endif + item->image = pixman_image_ref(image); + ring_item_init(&item->lru_link); + + item->next = cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE]; + cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE] = item; + + ring_add(&cache->lru, &item->lru_link); +} + +static pixman_image_t *image_cache_get(SpiceImageCache *spice_cache, uint64_t id) +{ + ImageCache *cache = (ImageCache *)spice_cache; + + ImageCacheItem *item = image_cache_find(cache, id); + if (!item) { + spice_error("not found"); + } + return pixman_image_ref(item->image); +} + +void image_cache_init(ImageCache *cache) +{ + static SpiceImageCacheOps image_cache_ops = { + image_cache_put, + image_cache_get, + }; + + cache->base.ops = &image_cache_ops; + memset(cache->hash_table, 0, sizeof(cache->hash_table)); + ring_init(&cache->lru); +#ifdef IMAGE_CACHE_AGE + cache->age = 0; +#else + cache->num_items = 0; +#endif +} + +void image_cache_reset(ImageCache *cache) +{ + ImageCacheItem *item; + + while ((item = (ImageCacheItem *)ring_get_head(&cache->lru))) { + image_cache_remove(cache, item); + } +#ifdef IMAGE_CACHE_AGE + cache->age = 0; +#endif +} + +#define IMAGE_CACHE_DEPTH 4 + +void image_cache_aging(ImageCache *cache) +{ +#ifdef IMAGE_CACHE_AGE + ImageCacheItem *item; + + cache->age++; + while ((item = (ImageCacheItem *)ring_get_tail(&cache->lru)) && + cache->age - item->age > IMAGE_CACHE_DEPTH) { + image_cache_remove(cache, item); + } +#endif +} + +void image_cache_localize(ImageCache *cache, SpiceImage **image_ptr, + SpiceImage *image_store, Drawable *drawable) +{ + SpiceImage *image = *image_ptr; + + if (image == NULL) { + spice_assert(drawable != NULL); + spice_assert(drawable->red_drawable->self_bitmap_image != NULL); + *image_ptr = drawable->red_drawable->self_bitmap_image; + return; + } + + if (image_cache_hit(cache, image->descriptor.id)) { + image_store->descriptor = image->descriptor; + image_store->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE; + image_store->descriptor.flags = 0; + *image_ptr = image_store; + return; + } + + switch (image->descriptor.type) { + case SPICE_IMAGE_TYPE_QUIC: { + image_store->descriptor = image->descriptor; + image_store->u.quic = image->u.quic; + *image_ptr = image_store; +#ifdef IMAGE_CACHE_AGE + image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME; +#else + if (image_store->descriptor.width * image->descriptor.height >= 640 * 480) { + image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME; + } +#endif + break; + } + case SPICE_IMAGE_TYPE_BITMAP: + case SPICE_IMAGE_TYPE_SURFACE: + /* nothing */ + break; + default: + spice_error("invalid image type"); + } +} + +void image_cache_localize_brush(ImageCache *cache, SpiceBrush *brush, SpiceImage *image_store) +{ + if (brush->type == SPICE_BRUSH_TYPE_PATTERN) { + image_cache_localize(cache, &brush->u.pattern.pat, image_store, NULL); + } +} + +void image_cache_localize_mask(ImageCache *cache, SpiceQMask *mask, SpiceImage *image_store) +{ + if (mask->bitmap) { + image_cache_localize(cache, &mask->bitmap, image_store, NULL); + } +} diff --git a/server/image-cache.h b/server/image-cache.h new file mode 100644 index 0000000..d66c7ff --- /dev/null +++ b/server/image-cache.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef IMAGE_CACHE_H_ +#define IMAGE_CACHE_H_ + +#include <inttypes.h> + +#include "common/pixman_utils.h" +#include "common/canvas_base.h" +#include "common/ring.h" + +/* FIXME: move back to display_channel.h (once structs are private) */ +typedef struct Drawable Drawable; +typedef struct DisplayChannelClient DisplayChannelClient; + +typedef struct ImageCacheItem { + RingItem lru_link; + uint64_t id; +#ifdef IMAGE_CACHE_AGE + uint32_t age; +#endif + struct ImageCacheItem *next; + pixman_image_t *image; +} ImageCacheItem; + +#define IMAGE_CACHE_HASH_SIZE 1024 + +typedef struct ImageCache { + SpiceImageCache base; + ImageCacheItem *hash_table[IMAGE_CACHE_HASH_SIZE]; + Ring lru; +#ifdef IMAGE_CACHE_AGE + uint32_t age; +#else + uint32_t num_items; +#endif +} ImageCache; + +int image_cache_hit (ImageCache *cache, uint64_t id); +void image_cache_init (ImageCache *cache); +void image_cache_reset (ImageCache *cache); +void image_cache_aging (ImageCache *cache); +void image_cache_localize (ImageCache *cache, SpiceImage **image_ptr, + SpiceImage *image_store, Drawable *drawable); +void image_cache_localize_brush (ImageCache *cache, SpiceBrush *brush, + SpiceImage *image_store); +void image_cache_localize_mask (ImageCache *cache, SpiceQMask *mask, + SpiceImage *image_store); + +#endif diff --git a/server/inputs-channel.c b/server/inputs-channel.c new file mode 100644 index 0000000..3e8fccd --- /dev/null +++ b/server/inputs-channel.c @@ -0,0 +1,679 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <netinet/in.h> // IPPROTO_TCP +#include <netinet/tcp.h> // TCP_NODELAY +#include <fcntl.h> +#include <stddef.h> // NULL +#include <errno.h> +#include <spice/macros.h> +#include <spice/vd_agent.h> +#include <spice/protocol.h> +#include <stdbool.h> + +#include "common/marshaller.h" +#include "common/messages.h" +#include "common/generated_server_marshallers.h" + +#include "demarshallers.h" +#include "spice.h" +#include "red_common.h" +#include "reds.h" +#include "reds_stream.h" +#include "red_channel.h" +#include "main-channel.h" +#include "inputs-channel.h" +#include "migration-protocol.h" + +// TODO: RECEIVE_BUF_SIZE used to be the same for inputs_channel and main_channel +// since it was defined once in reds.c which contained both. +// Now that they are split we can give a more fitting value for inputs - what +// should it be? +#define REDS_AGENT_WINDOW_SIZE 10 +#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1 + +// approximate max receive message size +#define RECEIVE_BUF_SIZE \ + (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * SPICE_AGENT_MAX_DATA_SIZE) + +struct SpiceKbdState { + bool push_ext; + + /* track key press state */ + bool key[0x7f]; + bool key_ext[0x7f]; +}; + +struct SpiceMouseState { + int dummy; +}; + +struct SpiceTabletState { + int dummy; +}; + +typedef struct InputsChannelClient { + RedChannelClient base; + uint16_t motion_count; +} InputsChannelClient; + +typedef struct InputsChannel { + RedChannel base; + uint8_t recv_buf[RECEIVE_BUF_SIZE]; + VDAgentMouseState mouse_state; + int src_during_migrate; +} InputsChannel; + +enum { + PIPE_ITEM_INPUTS_INIT = PIPE_ITEM_TYPE_CHANNEL_BASE, + PIPE_ITEM_MOUSE_MOTION_ACK, + PIPE_ITEM_KEY_MODIFIERS, + PIPE_ITEM_MIGRATE_DATA, +}; + +typedef struct InputsPipeItem { + PipeItem base; +} InputsPipeItem; + +typedef struct KeyModifiersPipeItem { + PipeItem base; + uint8_t modifiers; +} KeyModifiersPipeItem; + +typedef struct InputsInitPipeItem { + PipeItem base; + uint8_t modifiers; +} InputsInitPipeItem; + +static SpiceKbdInstance *keyboard = NULL; +static SpiceMouseInstance *mouse = NULL; +static SpiceTabletInstance *tablet = NULL; + +static SpiceTimer *key_modifiers_timer; + +static InputsChannel *g_inputs_channel = NULL; + +#define KEY_MODIFIERS_TTL (1000 * 2) /*2sec*/ + +#define SCROLL_LOCK_SCAN_CODE 0x46 +#define NUM_LOCK_SCAN_CODE 0x45 +#define CAPS_LOCK_SCAN_CODE 0x3a + +int inputs_inited(void) +{ + return !!g_inputs_channel; +} + +int inputs_set_keyboard(SpiceKbdInstance *_keyboard) +{ + if (keyboard) { + spice_printerr("already have keyboard"); + return -1; + } + keyboard = _keyboard; + keyboard->st = spice_new0(SpiceKbdState, 1); + return 0; +} + +int inputs_set_mouse(SpiceMouseInstance *_mouse) +{ + if (mouse) { + spice_printerr("already have mouse"); + return -1; + } + mouse = _mouse; + mouse->st = spice_new0(SpiceMouseState, 1); + return 0; +} + +int inputs_set_tablet(SpiceTabletInstance *_tablet) +{ + if (tablet) { + spice_printerr("already have tablet"); + return -1; + } + tablet = _tablet; + tablet->st = spice_new0(SpiceTabletState, 1); + return 0; +} + +int inputs_has_tablet(void) +{ + return !!tablet; +} + +void inputs_detach_tablet(SpiceTabletInstance *_tablet) +{ + spice_printerr(""); + tablet = NULL; +} + +void inputs_set_tablet_logical_size(int x_res, int y_res) +{ + SpiceTabletInterface *sif; + + sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base); + sif->set_logical_size(tablet, x_res, y_res); +} + +const VDAgentMouseState *inputs_get_mouse_state(void) +{ + spice_assert(g_inputs_channel); + return &g_inputs_channel->mouse_state; +} + +static uint8_t *inputs_channel_alloc_msg_rcv_buf(RedChannelClient *rcc, + uint16_t type, + uint32_t size) +{ + InputsChannel *inputs_channel = SPICE_CONTAINEROF(rcc->channel, InputsChannel, base); + + if (size > RECEIVE_BUF_SIZE) { + spice_printerr("error: too large incoming message"); + return NULL; + } + return inputs_channel->recv_buf; +} + +static void inputs_channel_release_msg_rcv_buf(RedChannelClient *rcc, + uint16_t type, + uint32_t size, + uint8_t *msg) +{ +} + +#define OUTGOING_OK 0 +#define OUTGOING_FAILED -1 +#define OUTGOING_BLOCKED 1 + +#define RED_MOUSE_STATE_TO_LOCAL(state) \ + ((state & SPICE_MOUSE_BUTTON_MASK_LEFT) | \ + ((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) << 1) | \ + ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) >> 1)) + +#define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) \ + (((state & SPICE_MOUSE_BUTTON_MASK_LEFT) ? VD_AGENT_LBUTTON_MASK : 0) | \ + ((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) ? VD_AGENT_MBUTTON_MASK : 0) | \ + ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) ? VD_AGENT_RBUTTON_MASK : 0)) + +static void activate_modifiers_watch(void) +{ + core->timer_start(key_modifiers_timer, KEY_MODIFIERS_TTL); +} + +static void kbd_push_scan(SpiceKbdInstance *sin, uint8_t scan) +{ + SpiceKbdInterface *sif; + + if (!sin) { + return; + } + sif = SPICE_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base); + + /* track XT scan code set 1 key state */ + if (scan == 0xe0) { + sin->st->push_ext = TRUE; + } else { + bool *state = sin->st->push_ext ? sin->st->key : sin->st->key_ext; + sin->st->push_ext = FALSE; + state[scan & 0x7f] = !(scan & 0x80); + } + + sif->push_scan_freg(sin, scan); +} + +static uint8_t kbd_get_leds(SpiceKbdInstance *sin) +{ + SpiceKbdInterface *sif; + + if (!sin) { + return 0; + } + sif = SPICE_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base); + return sif->get_leds(sin); +} + +static PipeItem *inputs_key_modifiers_item_new( + RedChannelClient *rcc, void *data, int num) +{ + KeyModifiersPipeItem *item = spice_malloc(sizeof(KeyModifiersPipeItem)); + + red_channel_pipe_item_init(rcc->channel, &item->base, + PIPE_ITEM_KEY_MODIFIERS); + item->modifiers = *(uint8_t *)data; + return &item->base; +} + +static void inputs_channel_send_migrate_data(RedChannelClient *rcc, + SpiceMarshaller *m, + PipeItem *item) +{ + InputsChannelClient *icc = SPICE_CONTAINEROF(rcc, InputsChannelClient, base); + + g_inputs_channel->src_during_migrate = FALSE; + red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item); + + spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_INPUTS_MAGIC); + spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_INPUTS_VERSION); + spice_marshaller_add_uint16(m, icc->motion_count); +} + +static void inputs_channel_release_pipe_item(RedChannelClient *rcc, + PipeItem *base, int item_pushed) +{ + free(base); +} + +static void inputs_channel_send_item(RedChannelClient *rcc, PipeItem *base) +{ + SpiceMarshaller *m = red_channel_client_get_marshaller(rcc); + + switch (base->type) { + case PIPE_ITEM_KEY_MODIFIERS: + { + SpiceMsgInputsKeyModifiers key_modifiers; + + red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_KEY_MODIFIERS, base); + key_modifiers.modifiers = + SPICE_CONTAINEROF(base, KeyModifiersPipeItem, base)->modifiers; + spice_marshall_msg_inputs_key_modifiers(m, &key_modifiers); + break; + } + case PIPE_ITEM_INPUTS_INIT: + { + SpiceMsgInputsInit inputs_init; + + red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_INIT, base); + inputs_init.keyboard_modifiers = + SPICE_CONTAINEROF(base, InputsInitPipeItem, base)->modifiers; + spice_marshall_msg_inputs_init(m, &inputs_init); + break; + } + case PIPE_ITEM_MOUSE_MOTION_ACK: + red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_MOUSE_MOTION_ACK, base); + break; + case PIPE_ITEM_MIGRATE_DATA: + inputs_channel_send_migrate_data(rcc, m, base); + break; + default: + spice_warning("invalid pipe iten %d", base->type); + break; + } + red_channel_client_begin_send_message(rcc); +} + +static int inputs_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type, + void *message) +{ + InputsChannel *inputs_channel = (InputsChannel *)rcc->channel; + InputsChannelClient *icc = (InputsChannelClient *)rcc; + uint32_t i; + + spice_assert(g_inputs_channel == inputs_channel); + switch (type) { + case SPICE_MSGC_INPUTS_KEY_DOWN: { + SpiceMsgcKeyDown *key_down = message; + if (key_down->code == CAPS_LOCK_SCAN_CODE || + key_down->code == NUM_LOCK_SCAN_CODE || + key_down->code == SCROLL_LOCK_SCAN_CODE) { + activate_modifiers_watch(); + } + } + case SPICE_MSGC_INPUTS_KEY_UP: { + SpiceMsgcKeyUp *key_up = message; + for (i = 0; i < 4; i++) { + uint8_t code = (key_up->code >> (i * 8)) & 0xff; + if (code == 0) { + break; + } + kbd_push_scan(keyboard, code); + } + break; + } + case SPICE_MSGC_INPUTS_KEY_SCANCODE: { + uint8_t *code = message; + for (i = 0; i < size; i++) { + kbd_push_scan(keyboard, code[i]); + } + break; + } + case SPICE_MSGC_INPUTS_MOUSE_MOTION: { + SpiceMsgcMouseMotion *mouse_motion = message; + + if (++icc->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0 && + !g_inputs_channel->src_during_migrate) { + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK); + icc->motion_count = 0; + } + if (mouse && reds_get_mouse_mode() == SPICE_MOUSE_MODE_SERVER) { + SpiceMouseInterface *sif; + sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base); + sif->motion(mouse, + mouse_motion->dx, mouse_motion->dy, 0, + RED_MOUSE_STATE_TO_LOCAL(mouse_motion->buttons_state)); + } + break; + } + case SPICE_MSGC_INPUTS_MOUSE_POSITION: { + SpiceMsgcMousePosition *pos = message; + + if (++icc->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0 && + !g_inputs_channel->src_during_migrate) { + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK); + icc->motion_count = 0; + } + if (reds_get_mouse_mode() != SPICE_MOUSE_MODE_CLIENT) { + break; + } + spice_assert((reds_get_agent_mouse() && reds_has_vdagent()) || tablet); + if (!reds_get_agent_mouse() || !reds_has_vdagent()) { + SpiceTabletInterface *sif; + sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base); + sif->position(tablet, pos->x, pos->y, RED_MOUSE_STATE_TO_LOCAL(pos->buttons_state)); + break; + } + VDAgentMouseState *mouse_state = &inputs_channel->mouse_state; + mouse_state->x = pos->x; + mouse_state->y = pos->y; + mouse_state->buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(pos->buttons_state); + mouse_state->display_id = pos->display_id; + reds_handle_agent_mouse_event(mouse_state); + break; + } + case SPICE_MSGC_INPUTS_MOUSE_PRESS: { + SpiceMsgcMousePress *mouse_press = message; + int dz = 0; + if (mouse_press->button == SPICE_MOUSE_BUTTON_UP) { + dz = -1; + } else if (mouse_press->button == SPICE_MOUSE_BUTTON_DOWN) { + dz = 1; + } + if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) { + if (reds_get_agent_mouse() && reds_has_vdagent()) { + inputs_channel->mouse_state.buttons = + RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_press->buttons_state) | + (dz == -1 ? VD_AGENT_UBUTTON_MASK : 0) | + (dz == 1 ? VD_AGENT_DBUTTON_MASK : 0); + reds_handle_agent_mouse_event(&inputs_channel->mouse_state); + } else if (tablet) { + SpiceTabletInterface *sif; + sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base); + sif->wheel(tablet, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state)); + } + } else if (mouse) { + SpiceMouseInterface *sif; + sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base); + sif->motion(mouse, 0, 0, dz, + RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state)); + } + break; + } + case SPICE_MSGC_INPUTS_MOUSE_RELEASE: { + SpiceMsgcMouseRelease *mouse_release = message; + if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) { + if (reds_get_agent_mouse() && reds_has_vdagent()) { + inputs_channel->mouse_state.buttons = + RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state); + reds_handle_agent_mouse_event(&inputs_channel->mouse_state); + } else if (tablet) { + SpiceTabletInterface *sif; + sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base); + sif->buttons(tablet, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state)); + } + } else if (mouse) { + SpiceMouseInterface *sif; + sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base); + sif->buttons(mouse, + RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state)); + } + break; + } + case SPICE_MSGC_INPUTS_KEY_MODIFIERS: { + SpiceMsgcKeyModifiers *modifiers = message; + uint8_t leds; + + if (!keyboard) { + break; + } + leds = kbd_get_leds(keyboard); + if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK) != + (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK)) { + kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE); + kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE | 0x80); + } + if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK) != + (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK)) { + kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE); + kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE | 0x80); + } + if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK) != + (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK)) { + kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE); + kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE | 0x80); + } + activate_modifiers_watch(); + break; + } + case SPICE_MSGC_DISCONNECTING: + break; + default: + return red_channel_client_handle_message(rcc, size, type, message); + } + return TRUE; +} + +static void inputs_release_keys(void) +{ + int i; + SpiceKbdState *st; + + if (!keyboard) { + return; + } + st = keyboard->st; + + for (i = 0; i < SPICE_N_ELEMENTS(st->key); i++) { + if (!st->key[i]) + continue; + + st->key[i] = FALSE; + kbd_push_scan(keyboard, i | 0x80); + } + + for (i = 0; i < SPICE_N_ELEMENTS(st->key_ext); i++) { + if (!st->key_ext[i]) + continue; + + st->key_ext[i] = FALSE; + kbd_push_scan(keyboard, 0xe0); + kbd_push_scan(keyboard, i | 0x80); + } +} + +static void inputs_channel_on_disconnect(RedChannelClient *rcc) +{ + if (!rcc) { + return; + } + inputs_release_keys(); +} + +static void inputs_pipe_add_init(RedChannelClient *rcc) +{ + InputsInitPipeItem *item = spice_malloc(sizeof(InputsInitPipeItem)); + + red_channel_pipe_item_init(rcc->channel, &item->base, + PIPE_ITEM_INPUTS_INIT); + item->modifiers = kbd_get_leds(keyboard); + red_channel_client_pipe_add_push(rcc, &item->base); +} + +static int inputs_channel_config_socket(RedChannelClient *rcc) +{ + int delay_val = 1; + RedsStream *stream = red_channel_client_get_stream(rcc); + + if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, + &delay_val, sizeof(delay_val)) == -1) { + if (errno != ENOTSUP && errno != ENOPROTOOPT) { + spice_printerr("setsockopt failed, %s", strerror(errno)); + return FALSE; + } + } + + return TRUE; +} + +static void inputs_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item) +{ +} + +static void inputs_connect(RedChannel *channel, RedClient *client, + RedsStream *stream, int migration, + int num_common_caps, uint32_t *common_caps, + int num_caps, uint32_t *caps) +{ + InputsChannelClient *icc; + + spice_assert(g_inputs_channel); + spice_assert(channel == &g_inputs_channel->base); + + if (!reds_stream_is_ssl(stream) && !red_client_during_migrate_at_target(client)) { + main_channel_client_push_notify(red_client_get_main(client), + "keyboard channel is insecure"); + } + + spice_printerr("inputs channel client create"); + icc = (InputsChannelClient*)red_channel_client_create(sizeof(InputsChannelClient), + channel, + client, + stream, + FALSE, + num_common_caps, common_caps, + num_caps, caps); + if (!icc) { + return; + } + icc->motion_count = 0; + inputs_pipe_add_init(&icc->base); +} + +static void inputs_migrate(RedChannelClient *rcc) +{ + g_inputs_channel->src_during_migrate = TRUE; + red_channel_client_default_migrate(rcc); +} + +static void inputs_push_keyboard_modifiers(uint8_t modifiers) +{ + if (!g_inputs_channel || !red_channel_is_connected(&g_inputs_channel->base) || + g_inputs_channel->src_during_migrate) { + return; + } + red_channel_pipes_new_add_push(&g_inputs_channel->base, + inputs_key_modifiers_item_new, (void*)&modifiers); +} + +void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds) +{ + inputs_push_keyboard_modifiers(leds); +} + +static void key_modifiers_sender(void *opaque) +{ + inputs_push_keyboard_modifiers(kbd_get_leds(keyboard)); +} + +static int inputs_channel_handle_migrate_flush_mark(RedChannelClient *rcc) +{ + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MIGRATE_DATA); + return TRUE; +} + +static int inputs_channel_handle_migrate_data(RedChannelClient *rcc, + uint32_t size, + void *message) +{ + InputsChannelClient *icc = SPICE_CONTAINEROF(rcc, InputsChannelClient, base); + SpiceMigrateDataHeader *header; + SpiceMigrateDataInputs *mig_data; + + header = (SpiceMigrateDataHeader *)message; + mig_data = (SpiceMigrateDataInputs *)(header + 1); + + if (!migration_protocol_validate_header(header, + SPICE_MIGRATE_DATA_INPUTS_MAGIC, + SPICE_MIGRATE_DATA_INPUTS_VERSION)) { + spice_error("bad header"); + return FALSE; + } + key_modifiers_sender(NULL); + icc->motion_count = mig_data->motion_count; + + for (; icc->motion_count >= SPICE_INPUT_MOTION_ACK_BUNCH; + icc->motion_count -= SPICE_INPUT_MOTION_ACK_BUNCH) { + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK); + } + return TRUE; +} + +void inputs_init(void) +{ + ChannelCbs channel_cbs = { NULL, }; + ClientCbs client_cbs = { NULL, }; + + spice_assert(!g_inputs_channel); + + channel_cbs.config_socket = inputs_channel_config_socket; + channel_cbs.on_disconnect = inputs_channel_on_disconnect; + channel_cbs.send_item = inputs_channel_send_item; + channel_cbs.hold_item = inputs_channel_hold_pipe_item; + channel_cbs.release_item = inputs_channel_release_pipe_item; + channel_cbs.alloc_recv_buf = inputs_channel_alloc_msg_rcv_buf; + channel_cbs.release_recv_buf = inputs_channel_release_msg_rcv_buf; + channel_cbs.handle_migrate_data = inputs_channel_handle_migrate_data; + channel_cbs.handle_migrate_flush_mark = inputs_channel_handle_migrate_flush_mark; + + g_inputs_channel = (InputsChannel *)red_channel_create_parser( + sizeof(InputsChannel), + core, + SPICE_CHANNEL_INPUTS, 0, + FALSE, /* handle_acks */ + spice_get_client_channel_parser(SPICE_CHANNEL_INPUTS, NULL), + inputs_channel_handle_parsed, + &channel_cbs, + SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER); + + if (!g_inputs_channel) { + spice_error("failed to allocate Inputs Channel"); + } + + client_cbs.connect = inputs_connect; + client_cbs.migrate = inputs_migrate; + red_channel_register_client_cbs(&g_inputs_channel->base, &client_cbs); + + red_channel_set_cap(&g_inputs_channel->base, SPICE_INPUTS_CAP_KEY_SCANCODE); + reds_register_channel(&g_inputs_channel->base); + + if (!(key_modifiers_timer = core->timer_add(key_modifiers_sender, NULL))) { + spice_error("key modifiers timer create failed"); + } +} diff --git a/server/inputs-channel.h b/server/inputs-channel.h new file mode 100644 index 0000000..7f7ace0 --- /dev/null +++ b/server/inputs-channel.h @@ -0,0 +1,38 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _INPUTS_CHANNEL_H_ +#define _INPUTS_CHANNEL_H_ + +// Inputs channel, dealing with keyboard, mouse, tablet. +// This include should only be used by reds.c and inputs-channel.c + +#include <stdint.h> +#include <spice/vd_agent.h> + +void inputs_init(void); +int inputs_inited(void); +int inputs_has_tablet(void); +const VDAgentMouseState *inputs_get_mouse_state(void); +void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds); +int inputs_set_keyboard(SpiceKbdInstance *_keyboard); +int inputs_set_mouse(SpiceMouseInstance *_mouse); +int inputs_set_tablet(SpiceTabletInstance *_tablet); +void inputs_detach_tablet(SpiceTabletInstance *_tablet); +void inputs_set_tablet_logical_size(int x_res, int y_res); + +#endif diff --git a/server/inputs_channel.c b/server/inputs_channel.c deleted file mode 100644 index 2934572..0000000 --- a/server/inputs_channel.c +++ /dev/null @@ -1,679 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <netinet/in.h> // IPPROTO_TCP -#include <netinet/tcp.h> // TCP_NODELAY -#include <fcntl.h> -#include <stddef.h> // NULL -#include <errno.h> -#include <spice/macros.h> -#include <spice/vd_agent.h> -#include <spice/protocol.h> -#include <stdbool.h> - -#include "common/marshaller.h" -#include "common/messages.h" -#include "common/generated_server_marshallers.h" - -#include "demarshallers.h" -#include "spice.h" -#include "red_common.h" -#include "reds.h" -#include "reds_stream.h" -#include "red_channel.h" -#include "main_channel.h" -#include "inputs_channel.h" -#include "migration_protocol.h" - -// TODO: RECEIVE_BUF_SIZE used to be the same for inputs_channel and main_channel -// since it was defined once in reds.c which contained both. -// Now that they are split we can give a more fitting value for inputs - what -// should it be? -#define REDS_AGENT_WINDOW_SIZE 10 -#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1 - -// approximate max receive message size -#define RECEIVE_BUF_SIZE \ - (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * SPICE_AGENT_MAX_DATA_SIZE) - -struct SpiceKbdState { - bool push_ext; - - /* track key press state */ - bool key[0x7f]; - bool key_ext[0x7f]; -}; - -struct SpiceMouseState { - int dummy; -}; - -struct SpiceTabletState { - int dummy; -}; - -typedef struct InputsChannelClient { - RedChannelClient base; - uint16_t motion_count; -} InputsChannelClient; - -typedef struct InputsChannel { - RedChannel base; - uint8_t recv_buf[RECEIVE_BUF_SIZE]; - VDAgentMouseState mouse_state; - int src_during_migrate; -} InputsChannel; - -enum { - PIPE_ITEM_INPUTS_INIT = PIPE_ITEM_TYPE_CHANNEL_BASE, - PIPE_ITEM_MOUSE_MOTION_ACK, - PIPE_ITEM_KEY_MODIFIERS, - PIPE_ITEM_MIGRATE_DATA, -}; - -typedef struct InputsPipeItem { - PipeItem base; -} InputsPipeItem; - -typedef struct KeyModifiersPipeItem { - PipeItem base; - uint8_t modifiers; -} KeyModifiersPipeItem; - -typedef struct InputsInitPipeItem { - PipeItem base; - uint8_t modifiers; -} InputsInitPipeItem; - -static SpiceKbdInstance *keyboard = NULL; -static SpiceMouseInstance *mouse = NULL; -static SpiceTabletInstance *tablet = NULL; - -static SpiceTimer *key_modifiers_timer; - -static InputsChannel *g_inputs_channel = NULL; - -#define KEY_MODIFIERS_TTL (1000 * 2) /*2sec*/ - -#define SCROLL_LOCK_SCAN_CODE 0x46 -#define NUM_LOCK_SCAN_CODE 0x45 -#define CAPS_LOCK_SCAN_CODE 0x3a - -int inputs_inited(void) -{ - return !!g_inputs_channel; -} - -int inputs_set_keyboard(SpiceKbdInstance *_keyboard) -{ - if (keyboard) { - spice_printerr("already have keyboard"); - return -1; - } - keyboard = _keyboard; - keyboard->st = spice_new0(SpiceKbdState, 1); - return 0; -} - -int inputs_set_mouse(SpiceMouseInstance *_mouse) -{ - if (mouse) { - spice_printerr("already have mouse"); - return -1; - } - mouse = _mouse; - mouse->st = spice_new0(SpiceMouseState, 1); - return 0; -} - -int inputs_set_tablet(SpiceTabletInstance *_tablet) -{ - if (tablet) { - spice_printerr("already have tablet"); - return -1; - } - tablet = _tablet; - tablet->st = spice_new0(SpiceTabletState, 1); - return 0; -} - -int inputs_has_tablet(void) -{ - return !!tablet; -} - -void inputs_detach_tablet(SpiceTabletInstance *_tablet) -{ - spice_printerr(""); - tablet = NULL; -} - -void inputs_set_tablet_logical_size(int x_res, int y_res) -{ - SpiceTabletInterface *sif; - - sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base); - sif->set_logical_size(tablet, x_res, y_res); -} - -const VDAgentMouseState *inputs_get_mouse_state(void) -{ - spice_assert(g_inputs_channel); - return &g_inputs_channel->mouse_state; -} - -static uint8_t *inputs_channel_alloc_msg_rcv_buf(RedChannelClient *rcc, - uint16_t type, - uint32_t size) -{ - InputsChannel *inputs_channel = SPICE_CONTAINEROF(rcc->channel, InputsChannel, base); - - if (size > RECEIVE_BUF_SIZE) { - spice_printerr("error: too large incoming message"); - return NULL; - } - return inputs_channel->recv_buf; -} - -static void inputs_channel_release_msg_rcv_buf(RedChannelClient *rcc, - uint16_t type, - uint32_t size, - uint8_t *msg) -{ -} - -#define OUTGOING_OK 0 -#define OUTGOING_FAILED -1 -#define OUTGOING_BLOCKED 1 - -#define RED_MOUSE_STATE_TO_LOCAL(state) \ - ((state & SPICE_MOUSE_BUTTON_MASK_LEFT) | \ - ((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) << 1) | \ - ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) >> 1)) - -#define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) \ - (((state & SPICE_MOUSE_BUTTON_MASK_LEFT) ? VD_AGENT_LBUTTON_MASK : 0) | \ - ((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) ? VD_AGENT_MBUTTON_MASK : 0) | \ - ((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) ? VD_AGENT_RBUTTON_MASK : 0)) - -static void activate_modifiers_watch(void) -{ - core->timer_start(key_modifiers_timer, KEY_MODIFIERS_TTL); -} - -static void kbd_push_scan(SpiceKbdInstance *sin, uint8_t scan) -{ - SpiceKbdInterface *sif; - - if (!sin) { - return; - } - sif = SPICE_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base); - - /* track XT scan code set 1 key state */ - if (scan == 0xe0) { - sin->st->push_ext = TRUE; - } else { - bool *state = sin->st->push_ext ? sin->st->key : sin->st->key_ext; - sin->st->push_ext = FALSE; - state[scan & 0x7f] = !(scan & 0x80); - } - - sif->push_scan_freg(sin, scan); -} - -static uint8_t kbd_get_leds(SpiceKbdInstance *sin) -{ - SpiceKbdInterface *sif; - - if (!sin) { - return 0; - } - sif = SPICE_CONTAINEROF(sin->base.sif, SpiceKbdInterface, base); - return sif->get_leds(sin); -} - -static PipeItem *inputs_key_modifiers_item_new( - RedChannelClient *rcc, void *data, int num) -{ - KeyModifiersPipeItem *item = spice_malloc(sizeof(KeyModifiersPipeItem)); - - red_channel_pipe_item_init(rcc->channel, &item->base, - PIPE_ITEM_KEY_MODIFIERS); - item->modifiers = *(uint8_t *)data; - return &item->base; -} - -static void inputs_channel_send_migrate_data(RedChannelClient *rcc, - SpiceMarshaller *m, - PipeItem *item) -{ - InputsChannelClient *icc = SPICE_CONTAINEROF(rcc, InputsChannelClient, base); - - g_inputs_channel->src_during_migrate = FALSE; - red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item); - - spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_INPUTS_MAGIC); - spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_INPUTS_VERSION); - spice_marshaller_add_uint16(m, icc->motion_count); -} - -static void inputs_channel_release_pipe_item(RedChannelClient *rcc, - PipeItem *base, int item_pushed) -{ - free(base); -} - -static void inputs_channel_send_item(RedChannelClient *rcc, PipeItem *base) -{ - SpiceMarshaller *m = red_channel_client_get_marshaller(rcc); - - switch (base->type) { - case PIPE_ITEM_KEY_MODIFIERS: - { - SpiceMsgInputsKeyModifiers key_modifiers; - - red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_KEY_MODIFIERS, base); - key_modifiers.modifiers = - SPICE_CONTAINEROF(base, KeyModifiersPipeItem, base)->modifiers; - spice_marshall_msg_inputs_key_modifiers(m, &key_modifiers); - break; - } - case PIPE_ITEM_INPUTS_INIT: - { - SpiceMsgInputsInit inputs_init; - - red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_INIT, base); - inputs_init.keyboard_modifiers = - SPICE_CONTAINEROF(base, InputsInitPipeItem, base)->modifiers; - spice_marshall_msg_inputs_init(m, &inputs_init); - break; - } - case PIPE_ITEM_MOUSE_MOTION_ACK: - red_channel_client_init_send_data(rcc, SPICE_MSG_INPUTS_MOUSE_MOTION_ACK, base); - break; - case PIPE_ITEM_MIGRATE_DATA: - inputs_channel_send_migrate_data(rcc, m, base); - break; - default: - spice_warning("invalid pipe iten %d", base->type); - break; - } - red_channel_client_begin_send_message(rcc); -} - -static int inputs_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type, - void *message) -{ - InputsChannel *inputs_channel = (InputsChannel *)rcc->channel; - InputsChannelClient *icc = (InputsChannelClient *)rcc; - uint32_t i; - - spice_assert(g_inputs_channel == inputs_channel); - switch (type) { - case SPICE_MSGC_INPUTS_KEY_DOWN: { - SpiceMsgcKeyDown *key_down = message; - if (key_down->code == CAPS_LOCK_SCAN_CODE || - key_down->code == NUM_LOCK_SCAN_CODE || - key_down->code == SCROLL_LOCK_SCAN_CODE) { - activate_modifiers_watch(); - } - } - case SPICE_MSGC_INPUTS_KEY_UP: { - SpiceMsgcKeyUp *key_up = message; - for (i = 0; i < 4; i++) { - uint8_t code = (key_up->code >> (i * 8)) & 0xff; - if (code == 0) { - break; - } - kbd_push_scan(keyboard, code); - } - break; - } - case SPICE_MSGC_INPUTS_KEY_SCANCODE: { - uint8_t *code = message; - for (i = 0; i < size; i++) { - kbd_push_scan(keyboard, code[i]); - } - break; - } - case SPICE_MSGC_INPUTS_MOUSE_MOTION: { - SpiceMsgcMouseMotion *mouse_motion = message; - - if (++icc->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0 && - !g_inputs_channel->src_during_migrate) { - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK); - icc->motion_count = 0; - } - if (mouse && reds_get_mouse_mode() == SPICE_MOUSE_MODE_SERVER) { - SpiceMouseInterface *sif; - sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base); - sif->motion(mouse, - mouse_motion->dx, mouse_motion->dy, 0, - RED_MOUSE_STATE_TO_LOCAL(mouse_motion->buttons_state)); - } - break; - } - case SPICE_MSGC_INPUTS_MOUSE_POSITION: { - SpiceMsgcMousePosition *pos = message; - - if (++icc->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0 && - !g_inputs_channel->src_during_migrate) { - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK); - icc->motion_count = 0; - } - if (reds_get_mouse_mode() != SPICE_MOUSE_MODE_CLIENT) { - break; - } - spice_assert((reds_get_agent_mouse() && reds_has_vdagent()) || tablet); - if (!reds_get_agent_mouse() || !reds_has_vdagent()) { - SpiceTabletInterface *sif; - sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base); - sif->position(tablet, pos->x, pos->y, RED_MOUSE_STATE_TO_LOCAL(pos->buttons_state)); - break; - } - VDAgentMouseState *mouse_state = &inputs_channel->mouse_state; - mouse_state->x = pos->x; - mouse_state->y = pos->y; - mouse_state->buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(pos->buttons_state); - mouse_state->display_id = pos->display_id; - reds_handle_agent_mouse_event(mouse_state); - break; - } - case SPICE_MSGC_INPUTS_MOUSE_PRESS: { - SpiceMsgcMousePress *mouse_press = message; - int dz = 0; - if (mouse_press->button == SPICE_MOUSE_BUTTON_UP) { - dz = -1; - } else if (mouse_press->button == SPICE_MOUSE_BUTTON_DOWN) { - dz = 1; - } - if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) { - if (reds_get_agent_mouse() && reds_has_vdagent()) { - inputs_channel->mouse_state.buttons = - RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_press->buttons_state) | - (dz == -1 ? VD_AGENT_UBUTTON_MASK : 0) | - (dz == 1 ? VD_AGENT_DBUTTON_MASK : 0); - reds_handle_agent_mouse_event(&inputs_channel->mouse_state); - } else if (tablet) { - SpiceTabletInterface *sif; - sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base); - sif->wheel(tablet, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state)); - } - } else if (mouse) { - SpiceMouseInterface *sif; - sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base); - sif->motion(mouse, 0, 0, dz, - RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state)); - } - break; - } - case SPICE_MSGC_INPUTS_MOUSE_RELEASE: { - SpiceMsgcMouseRelease *mouse_release = message; - if (reds_get_mouse_mode() == SPICE_MOUSE_MODE_CLIENT) { - if (reds_get_agent_mouse() && reds_has_vdagent()) { - inputs_channel->mouse_state.buttons = - RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state); - reds_handle_agent_mouse_event(&inputs_channel->mouse_state); - } else if (tablet) { - SpiceTabletInterface *sif; - sif = SPICE_CONTAINEROF(tablet->base.sif, SpiceTabletInterface, base); - sif->buttons(tablet, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state)); - } - } else if (mouse) { - SpiceMouseInterface *sif; - sif = SPICE_CONTAINEROF(mouse->base.sif, SpiceMouseInterface, base); - sif->buttons(mouse, - RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state)); - } - break; - } - case SPICE_MSGC_INPUTS_KEY_MODIFIERS: { - SpiceMsgcKeyModifiers *modifiers = message; - uint8_t leds; - - if (!keyboard) { - break; - } - leds = kbd_get_leds(keyboard); - if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK) != - (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK)) { - kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE); - kbd_push_scan(keyboard, SCROLL_LOCK_SCAN_CODE | 0x80); - } - if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK) != - (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK)) { - kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE); - kbd_push_scan(keyboard, NUM_LOCK_SCAN_CODE | 0x80); - } - if ((modifiers->modifiers & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK) != - (leds & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK)) { - kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE); - kbd_push_scan(keyboard, CAPS_LOCK_SCAN_CODE | 0x80); - } - activate_modifiers_watch(); - break; - } - case SPICE_MSGC_DISCONNECTING: - break; - default: - return red_channel_client_handle_message(rcc, size, type, message); - } - return TRUE; -} - -static void inputs_release_keys(void) -{ - int i; - SpiceKbdState *st; - - if (!keyboard) { - return; - } - st = keyboard->st; - - for (i = 0; i < SPICE_N_ELEMENTS(st->key); i++) { - if (!st->key[i]) - continue; - - st->key[i] = FALSE; - kbd_push_scan(keyboard, i | 0x80); - } - - for (i = 0; i < SPICE_N_ELEMENTS(st->key_ext); i++) { - if (!st->key_ext[i]) - continue; - - st->key_ext[i] = FALSE; - kbd_push_scan(keyboard, 0xe0); - kbd_push_scan(keyboard, i | 0x80); - } -} - -static void inputs_channel_on_disconnect(RedChannelClient *rcc) -{ - if (!rcc) { - return; - } - inputs_release_keys(); -} - -static void inputs_pipe_add_init(RedChannelClient *rcc) -{ - InputsInitPipeItem *item = spice_malloc(sizeof(InputsInitPipeItem)); - - red_channel_pipe_item_init(rcc->channel, &item->base, - PIPE_ITEM_INPUTS_INIT); - item->modifiers = kbd_get_leds(keyboard); - red_channel_client_pipe_add_push(rcc, &item->base); -} - -static int inputs_channel_config_socket(RedChannelClient *rcc) -{ - int delay_val = 1; - RedsStream *stream = red_channel_client_get_stream(rcc); - - if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, - &delay_val, sizeof(delay_val)) == -1) { - if (errno != ENOTSUP && errno != ENOPROTOOPT) { - spice_printerr("setsockopt failed, %s", strerror(errno)); - return FALSE; - } - } - - return TRUE; -} - -static void inputs_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item) -{ -} - -static void inputs_connect(RedChannel *channel, RedClient *client, - RedsStream *stream, int migration, - int num_common_caps, uint32_t *common_caps, - int num_caps, uint32_t *caps) -{ - InputsChannelClient *icc; - - spice_assert(g_inputs_channel); - spice_assert(channel == &g_inputs_channel->base); - - if (!reds_stream_is_ssl(stream) && !red_client_during_migrate_at_target(client)) { - main_channel_client_push_notify(red_client_get_main(client), - "keyboard channel is insecure"); - } - - spice_printerr("inputs channel client create"); - icc = (InputsChannelClient*)red_channel_client_create(sizeof(InputsChannelClient), - channel, - client, - stream, - FALSE, - num_common_caps, common_caps, - num_caps, caps); - if (!icc) { - return; - } - icc->motion_count = 0; - inputs_pipe_add_init(&icc->base); -} - -static void inputs_migrate(RedChannelClient *rcc) -{ - g_inputs_channel->src_during_migrate = TRUE; - red_channel_client_default_migrate(rcc); -} - -static void inputs_push_keyboard_modifiers(uint8_t modifiers) -{ - if (!g_inputs_channel || !red_channel_is_connected(&g_inputs_channel->base) || - g_inputs_channel->src_during_migrate) { - return; - } - red_channel_pipes_new_add_push(&g_inputs_channel->base, - inputs_key_modifiers_item_new, (void*)&modifiers); -} - -void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds) -{ - inputs_push_keyboard_modifiers(leds); -} - -static void key_modifiers_sender(void *opaque) -{ - inputs_push_keyboard_modifiers(kbd_get_leds(keyboard)); -} - -static int inputs_channel_handle_migrate_flush_mark(RedChannelClient *rcc) -{ - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MIGRATE_DATA); - return TRUE; -} - -static int inputs_channel_handle_migrate_data(RedChannelClient *rcc, - uint32_t size, - void *message) -{ - InputsChannelClient *icc = SPICE_CONTAINEROF(rcc, InputsChannelClient, base); - SpiceMigrateDataHeader *header; - SpiceMigrateDataInputs *mig_data; - - header = (SpiceMigrateDataHeader *)message; - mig_data = (SpiceMigrateDataInputs *)(header + 1); - - if (!migration_protocol_validate_header(header, - SPICE_MIGRATE_DATA_INPUTS_MAGIC, - SPICE_MIGRATE_DATA_INPUTS_VERSION)) { - spice_error("bad header"); - return FALSE; - } - key_modifiers_sender(NULL); - icc->motion_count = mig_data->motion_count; - - for (; icc->motion_count >= SPICE_INPUT_MOTION_ACK_BUNCH; - icc->motion_count -= SPICE_INPUT_MOTION_ACK_BUNCH) { - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_MOUSE_MOTION_ACK); - } - return TRUE; -} - -void inputs_init(void) -{ - ChannelCbs channel_cbs = { NULL, }; - ClientCbs client_cbs = { NULL, }; - - spice_assert(!g_inputs_channel); - - channel_cbs.config_socket = inputs_channel_config_socket; - channel_cbs.on_disconnect = inputs_channel_on_disconnect; - channel_cbs.send_item = inputs_channel_send_item; - channel_cbs.hold_item = inputs_channel_hold_pipe_item; - channel_cbs.release_item = inputs_channel_release_pipe_item; - channel_cbs.alloc_recv_buf = inputs_channel_alloc_msg_rcv_buf; - channel_cbs.release_recv_buf = inputs_channel_release_msg_rcv_buf; - channel_cbs.handle_migrate_data = inputs_channel_handle_migrate_data; - channel_cbs.handle_migrate_flush_mark = inputs_channel_handle_migrate_flush_mark; - - g_inputs_channel = (InputsChannel *)red_channel_create_parser( - sizeof(InputsChannel), - core, - SPICE_CHANNEL_INPUTS, 0, - FALSE, /* handle_acks */ - spice_get_client_channel_parser(SPICE_CHANNEL_INPUTS, NULL), - inputs_channel_handle_parsed, - &channel_cbs, - SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER); - - if (!g_inputs_channel) { - spice_error("failed to allocate Inputs Channel"); - } - - client_cbs.connect = inputs_connect; - client_cbs.migrate = inputs_migrate; - red_channel_register_client_cbs(&g_inputs_channel->base, &client_cbs); - - red_channel_set_cap(&g_inputs_channel->base, SPICE_INPUTS_CAP_KEY_SCANCODE); - reds_register_channel(&g_inputs_channel->base); - - if (!(key_modifiers_timer = core->timer_add(key_modifiers_sender, NULL))) { - spice_error("key modifiers timer create failed"); - } -} diff --git a/server/inputs_channel.h b/server/inputs_channel.h deleted file mode 100644 index 672ca83..0000000 --- a/server/inputs_channel.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef _INPUTS_CHANNEL_H_ -#define _INPUTS_CHANNEL_H_ - -// Inputs channel, dealing with keyboard, mouse, tablet. -// This include should only be used by reds.c and inputs_channel.c - -#include <stdint.h> -#include <spice/vd_agent.h> - -void inputs_init(void); -int inputs_inited(void); -int inputs_has_tablet(void); -const VDAgentMouseState *inputs_get_mouse_state(void); -void inputs_on_keyboard_leds_change(void *opaque, uint8_t leds); -int inputs_set_keyboard(SpiceKbdInstance *_keyboard); -int inputs_set_mouse(SpiceMouseInstance *_mouse); -int inputs_set_tablet(SpiceTabletInstance *_tablet); -void inputs_detach_tablet(SpiceTabletInstance *_tablet); -void inputs_set_tablet_logical_size(int x_res, int y_res); - -#endif diff --git a/server/jpeg-encoder.c b/server/jpeg-encoder.c new file mode 100644 index 0000000..8e54dd2 --- /dev/null +++ b/server/jpeg-encoder.c @@ -0,0 +1,248 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "red_common.h" +#include "jpeg-encoder.h" +#include <jpeglib.h> + +typedef struct JpegEncoder { + JpegEncoderUsrContext *usr; + + struct jpeg_destination_mgr dest_mgr; + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + + struct { + JpegEncoderImageType type; + int width; + int height; + int stride; + unsigned int out_size; + void (*convert_line_to_RGB24) (void *line, int width, uint8_t **out_line); + } cur_image; +} JpegEncoder; + +/* jpeg destination manager callbacks */ + +static void dest_mgr_init_destination(j_compress_ptr cinfo) +{ + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; + if (enc->dest_mgr.free_in_buffer == 0) { + enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr, + &enc->dest_mgr.next_output_byte); + + if (enc->dest_mgr.free_in_buffer == 0) { + spice_error("not enough space"); + } + } + + enc->cur_image.out_size = enc->dest_mgr.free_in_buffer; +} + +static boolean dest_mgr_empty_output_buffer(j_compress_ptr cinfo) +{ + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; + enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr, + &enc->dest_mgr.next_output_byte); + + if (enc->dest_mgr.free_in_buffer == 0) { + spice_error("not enough space"); + } + enc->cur_image.out_size += enc->dest_mgr.free_in_buffer; + return TRUE; +} + +static void dest_mgr_term_destination(j_compress_ptr cinfo) +{ + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; + enc->cur_image.out_size -= enc->dest_mgr.free_in_buffer; +} + +JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr) +{ + JpegEncoder *enc; + if (!usr->more_space || !usr->more_lines) { + return NULL; + } + + enc = spice_new0(JpegEncoder, 1); + + enc->usr = usr; + + enc->dest_mgr.init_destination = dest_mgr_init_destination; + enc->dest_mgr.empty_output_buffer = dest_mgr_empty_output_buffer; + enc->dest_mgr.term_destination = dest_mgr_term_destination; + + enc->cinfo.err = jpeg_std_error(&enc->jerr); + + jpeg_create_compress(&enc->cinfo); + enc->cinfo.client_data = enc; + enc->cinfo.dest = &enc->dest_mgr; + return (JpegEncoderContext*)enc; +} + +void jpeg_encoder_destroy(JpegEncoderContext* encoder) +{ + jpeg_destroy_compress(&((JpegEncoder*)encoder)->cinfo); + free(encoder); +} + +static void convert_RGB16_to_RGB24(void *line, int width, uint8_t **out_line) +{ + uint16_t *src_line = line; + uint8_t *out_pix; + int x; + + spice_assert(out_line && *out_line); + + out_pix = *out_line; + + for (x = 0; x < width; x++) { + uint16_t pixel = *src_line++; + *out_pix++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7); + *out_pix++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7); + *out_pix++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7); + } +} + +static void convert_BGR24_to_RGB24(void *in_line, int width, uint8_t **out_line) +{ + int x; + uint8_t *out_pix; + uint8_t *line = in_line; + spice_assert(out_line && *out_line); + + out_pix = *out_line; + + for (x = 0; x < width; x++) { + *out_pix++ = line[2]; + *out_pix++ = line[1]; + *out_pix++ = line[0]; + line += 3; + } +} + +static void convert_BGRX32_to_RGB24(void *line, int width, uint8_t **out_line) +{ + uint32_t *src_line = line; + uint8_t *out_pix; + int x; + + spice_assert(out_line && *out_line); + + out_pix = *out_line; + + for (x = 0; x < width; x++) { + uint32_t pixel = *src_line++; + *out_pix++ = (pixel >> 16) & 0xff; + *out_pix++ = (pixel >> 8) & 0xff; + *out_pix++ = pixel & 0xff; + } +} + +static void convert_RGB24_to_RGB24(void *line, int width, uint8_t **out_line) +{ + *out_line = line; +} + + +#define FILL_LINES() { \ + if (lines == lines_end) { \ + int n = jpeg->usr->more_lines(jpeg->usr, &lines); \ + if (n <= 0) { \ + spice_error("more lines failed"); \ + } \ + lines_end = lines + n * stride; \ + } \ +} + +static void do_jpeg_encode(JpegEncoder *jpeg, uint8_t *lines, unsigned int num_lines) +{ + uint8_t *lines_end; + uint8_t *RGB24_line; + int stride, width; + JSAMPROW row_pointer[1]; + width = jpeg->cur_image.width; + stride = jpeg->cur_image.stride; + + if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) { + RGB24_line = (uint8_t *)spice_malloc(width*3); + } + + lines_end = lines + (stride * num_lines); + + for (;jpeg->cinfo.next_scanline < jpeg->cinfo.image_height; lines += stride) { + FILL_LINES(); + jpeg->cur_image.convert_line_to_RGB24(lines, width, &RGB24_line); + row_pointer[0] = RGB24_line; + jpeg_write_scanlines(&jpeg->cinfo, row_pointer, 1); + } + + if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) { + free(RGB24_line); + } +} + +int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type, + int width, int height, uint8_t *lines, unsigned int num_lines, int stride, + uint8_t *io_ptr, unsigned int num_io_bytes) +{ + JpegEncoder *enc = (JpegEncoder *)jpeg; + + enc->cur_image.type = type; + enc->cur_image.width = width; + enc->cur_image.height = height; + enc->cur_image.stride = stride; + enc->cur_image.out_size = 0; + + switch (type) { + case JPEG_IMAGE_TYPE_RGB16: + enc->cur_image.convert_line_to_RGB24 = convert_RGB16_to_RGB24; + break; + case JPEG_IMAGE_TYPE_RGB24: + enc->cur_image.convert_line_to_RGB24 = convert_RGB24_to_RGB24; + break; + case JPEG_IMAGE_TYPE_BGR24: + enc->cur_image.convert_line_to_RGB24 = convert_BGR24_to_RGB24; + break; + case JPEG_IMAGE_TYPE_BGRX32: + enc->cur_image.convert_line_to_RGB24 = convert_BGRX32_to_RGB24; + break; + default: + spice_error("bad image type"); + } + + enc->cinfo.image_width = width; + enc->cinfo.image_height = height; + enc->cinfo.input_components = 3; + enc->cinfo.in_color_space = JCS_RGB; + jpeg_set_defaults(&enc->cinfo); + jpeg_set_quality(&enc->cinfo, quality, TRUE); + + enc->dest_mgr.next_output_byte = io_ptr; + enc->dest_mgr.free_in_buffer = num_io_bytes; + + jpeg_start_compress(&enc->cinfo, TRUE); + + do_jpeg_encode(enc, lines, num_lines); + + jpeg_finish_compress(&enc->cinfo); + return enc->cur_image.out_size; +} diff --git a/server/jpeg-encoder.h b/server/jpeg-encoder.h new file mode 100644 index 0000000..690a029 --- /dev/null +++ b/server/jpeg-encoder.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef _H_JPEG_ENCODER +#define _H_JPEG_ENCODER + +#include <spice/types.h> + +typedef enum { + JPEG_IMAGE_TYPE_INVALID, + JPEG_IMAGE_TYPE_RGB16, + /* in byte per color types, the notation is according to the order of the + colors in the memory */ + JPEG_IMAGE_TYPE_RGB24, + JPEG_IMAGE_TYPE_BGR24, + JPEG_IMAGE_TYPE_BGRX32, +} JpegEncoderImageType; + +typedef void* JpegEncoderContext; +typedef struct JpegEncoderUsrContext JpegEncoderUsrContext; + +struct JpegEncoderUsrContext { + int (*more_space)(JpegEncoderUsrContext *usr, uint8_t **io_ptr); + int (*more_lines)(JpegEncoderUsrContext *usr, uint8_t **lines); +}; + +JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr); +void jpeg_encoder_destroy(JpegEncoderContext *encoder); + +/* returns the total size of the encoded data. Images must be supplied from the + top line to the bottom */ +int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type, + int width, int height, uint8_t *lines, unsigned int num_lines, int stride, + uint8_t *io_ptr, unsigned int num_io_bytes); +#endif diff --git a/server/jpeg_encoder.c b/server/jpeg_encoder.c deleted file mode 100644 index 0296e9b..0000000 --- a/server/jpeg_encoder.c +++ /dev/null @@ -1,248 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include "red_common.h" -#include "jpeg_encoder.h" -#include <jpeglib.h> - -typedef struct JpegEncoder { - JpegEncoderUsrContext *usr; - - struct jpeg_destination_mgr dest_mgr; - struct jpeg_compress_struct cinfo; - struct jpeg_error_mgr jerr; - - struct { - JpegEncoderImageType type; - int width; - int height; - int stride; - unsigned int out_size; - void (*convert_line_to_RGB24) (void *line, int width, uint8_t **out_line); - } cur_image; -} JpegEncoder; - -/* jpeg destination manager callbacks */ - -static void dest_mgr_init_destination(j_compress_ptr cinfo) -{ - JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; - if (enc->dest_mgr.free_in_buffer == 0) { - enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr, - &enc->dest_mgr.next_output_byte); - - if (enc->dest_mgr.free_in_buffer == 0) { - spice_error("not enough space"); - } - } - - enc->cur_image.out_size = enc->dest_mgr.free_in_buffer; -} - -static boolean dest_mgr_empty_output_buffer(j_compress_ptr cinfo) -{ - JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; - enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr, - &enc->dest_mgr.next_output_byte); - - if (enc->dest_mgr.free_in_buffer == 0) { - spice_error("not enough space"); - } - enc->cur_image.out_size += enc->dest_mgr.free_in_buffer; - return TRUE; -} - -static void dest_mgr_term_destination(j_compress_ptr cinfo) -{ - JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; - enc->cur_image.out_size -= enc->dest_mgr.free_in_buffer; -} - -JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr) -{ - JpegEncoder *enc; - if (!usr->more_space || !usr->more_lines) { - return NULL; - } - - enc = spice_new0(JpegEncoder, 1); - - enc->usr = usr; - - enc->dest_mgr.init_destination = dest_mgr_init_destination; - enc->dest_mgr.empty_output_buffer = dest_mgr_empty_output_buffer; - enc->dest_mgr.term_destination = dest_mgr_term_destination; - - enc->cinfo.err = jpeg_std_error(&enc->jerr); - - jpeg_create_compress(&enc->cinfo); - enc->cinfo.client_data = enc; - enc->cinfo.dest = &enc->dest_mgr; - return (JpegEncoderContext*)enc; -} - -void jpeg_encoder_destroy(JpegEncoderContext* encoder) -{ - jpeg_destroy_compress(&((JpegEncoder*)encoder)->cinfo); - free(encoder); -} - -static void convert_RGB16_to_RGB24(void *line, int width, uint8_t **out_line) -{ - uint16_t *src_line = line; - uint8_t *out_pix; - int x; - - spice_assert(out_line && *out_line); - - out_pix = *out_line; - - for (x = 0; x < width; x++) { - uint16_t pixel = *src_line++; - *out_pix++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7); - *out_pix++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7); - *out_pix++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7); - } -} - -static void convert_BGR24_to_RGB24(void *in_line, int width, uint8_t **out_line) -{ - int x; - uint8_t *out_pix; - uint8_t *line = in_line; - spice_assert(out_line && *out_line); - - out_pix = *out_line; - - for (x = 0; x < width; x++) { - *out_pix++ = line[2]; - *out_pix++ = line[1]; - *out_pix++ = line[0]; - line += 3; - } -} - -static void convert_BGRX32_to_RGB24(void *line, int width, uint8_t **out_line) -{ - uint32_t *src_line = line; - uint8_t *out_pix; - int x; - - spice_assert(out_line && *out_line); - - out_pix = *out_line; - - for (x = 0; x < width; x++) { - uint32_t pixel = *src_line++; - *out_pix++ = (pixel >> 16) & 0xff; - *out_pix++ = (pixel >> 8) & 0xff; - *out_pix++ = pixel & 0xff; - } -} - -static void convert_RGB24_to_RGB24(void *line, int width, uint8_t **out_line) -{ - *out_line = line; -} - - -#define FILL_LINES() { \ - if (lines == lines_end) { \ - int n = jpeg->usr->more_lines(jpeg->usr, &lines); \ - if (n <= 0) { \ - spice_error("more lines failed"); \ - } \ - lines_end = lines + n * stride; \ - } \ -} - -static void do_jpeg_encode(JpegEncoder *jpeg, uint8_t *lines, unsigned int num_lines) -{ - uint8_t *lines_end; - uint8_t *RGB24_line; - int stride, width; - JSAMPROW row_pointer[1]; - width = jpeg->cur_image.width; - stride = jpeg->cur_image.stride; - - if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) { - RGB24_line = (uint8_t *)spice_malloc(width*3); - } - - lines_end = lines + (stride * num_lines); - - for (;jpeg->cinfo.next_scanline < jpeg->cinfo.image_height; lines += stride) { - FILL_LINES(); - jpeg->cur_image.convert_line_to_RGB24(lines, width, &RGB24_line); - row_pointer[0] = RGB24_line; - jpeg_write_scanlines(&jpeg->cinfo, row_pointer, 1); - } - - if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) { - free(RGB24_line); - } -} - -int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type, - int width, int height, uint8_t *lines, unsigned int num_lines, int stride, - uint8_t *io_ptr, unsigned int num_io_bytes) -{ - JpegEncoder *enc = (JpegEncoder *)jpeg; - - enc->cur_image.type = type; - enc->cur_image.width = width; - enc->cur_image.height = height; - enc->cur_image.stride = stride; - enc->cur_image.out_size = 0; - - switch (type) { - case JPEG_IMAGE_TYPE_RGB16: - enc->cur_image.convert_line_to_RGB24 = convert_RGB16_to_RGB24; - break; - case JPEG_IMAGE_TYPE_RGB24: - enc->cur_image.convert_line_to_RGB24 = convert_RGB24_to_RGB24; - break; - case JPEG_IMAGE_TYPE_BGR24: - enc->cur_image.convert_line_to_RGB24 = convert_BGR24_to_RGB24; - break; - case JPEG_IMAGE_TYPE_BGRX32: - enc->cur_image.convert_line_to_RGB24 = convert_BGRX32_to_RGB24; - break; - default: - spice_error("bad image type"); - } - - enc->cinfo.image_width = width; - enc->cinfo.image_height = height; - enc->cinfo.input_components = 3; - enc->cinfo.in_color_space = JCS_RGB; - jpeg_set_defaults(&enc->cinfo); - jpeg_set_quality(&enc->cinfo, quality, TRUE); - - enc->dest_mgr.next_output_byte = io_ptr; - enc->dest_mgr.free_in_buffer = num_io_bytes; - - jpeg_start_compress(&enc->cinfo, TRUE); - - do_jpeg_encode(enc, lines, num_lines); - - jpeg_finish_compress(&enc->cinfo); - return enc->cur_image.out_size; -} diff --git a/server/jpeg_encoder.h b/server/jpeg_encoder.h deleted file mode 100644 index 690a029..0000000 --- a/server/jpeg_encoder.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#ifndef _H_JPEG_ENCODER -#define _H_JPEG_ENCODER - -#include <spice/types.h> - -typedef enum { - JPEG_IMAGE_TYPE_INVALID, - JPEG_IMAGE_TYPE_RGB16, - /* in byte per color types, the notation is according to the order of the - colors in the memory */ - JPEG_IMAGE_TYPE_RGB24, - JPEG_IMAGE_TYPE_BGR24, - JPEG_IMAGE_TYPE_BGRX32, -} JpegEncoderImageType; - -typedef void* JpegEncoderContext; -typedef struct JpegEncoderUsrContext JpegEncoderUsrContext; - -struct JpegEncoderUsrContext { - int (*more_space)(JpegEncoderUsrContext *usr, uint8_t **io_ptr); - int (*more_lines)(JpegEncoderUsrContext *usr, uint8_t **lines); -}; - -JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr); -void jpeg_encoder_destroy(JpegEncoderContext *encoder); - -/* returns the total size of the encoded data. Images must be supplied from the - top line to the bottom */ -int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type, - int width, int height, uint8_t *lines, unsigned int num_lines, int stride, - uint8_t *io_ptr, unsigned int num_io_bytes); -#endif diff --git a/server/main-channel.c b/server/main-channel.c new file mode 100644 index 0000000..5ca5bba --- /dev/null +++ b/server/main-channel.c @@ -0,0 +1,1345 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <limits.h> +#include <time.h> +#include <pthread.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> + +#include "common/generated_server_marshallers.h" +#include "common/messages.h" +#include "common/ring.h" + +#include "demarshallers.h" +#include "main-channel.h" +#include "red_channel.h" +#include "red_common.h" +#include "reds.h" +#include "migration-protocol.h" +#include "main-dispatcher.h" +#include "utils.h" + +#define ZERO_BUF_SIZE 4096 + +#define NET_TEST_WARMUP_BYTES 0 +#define NET_TEST_BYTES (1024 * 250) + +#define PING_INTERVAL (1000 * 10) + +#define CLIENT_CONNECTIVITY_TIMEOUT (30*1000) // 30 seconds + +static uint8_t zero_page[ZERO_BUF_SIZE] = {0}; + +enum { + PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST = PIPE_ITEM_TYPE_CHANNEL_BASE, + PIPE_ITEM_TYPE_MAIN_PING, + PIPE_ITEM_TYPE_MAIN_MOUSE_MODE, + PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED, + PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN, + PIPE_ITEM_TYPE_MAIN_AGENT_DATA, + PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA, + PIPE_ITEM_TYPE_MAIN_INIT, + PIPE_ITEM_TYPE_MAIN_NOTIFY, + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN, + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS, + PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST, + PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME, + PIPE_ITEM_TYPE_MAIN_NAME, + PIPE_ITEM_TYPE_MAIN_UUID, + PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS, +}; + +typedef struct RefsPipeItem { + PipeItem base; + int *refs; +} RefsPipeItem; + +typedef struct PingPipeItem { + PipeItem base; + int size; +} PingPipeItem; + +typedef struct MouseModePipeItem { + PipeItem base; + int current_mode; + int is_client_mouse_allowed; +} MouseModePipeItem; + +typedef struct TokensPipeItem { + PipeItem base; + int tokens; +} TokensPipeItem; + +typedef struct AgentDataPipeItem { + PipeItem base; + uint8_t* data; + size_t len; + spice_marshaller_item_free_func free_data; + void *opaque; +} AgentDataPipeItem; + +typedef struct InitPipeItem { + PipeItem base; + int connection_id; + int display_channels_hint; + int current_mouse_mode; + int is_client_mouse_allowed; + int multi_media_time; + int ram_hint; +} InitPipeItem; + +typedef struct NamePipeItem { + PipeItem base; + SpiceMsgMainName msg; +} NamePipeItem; + +typedef struct UuidPipeItem { + PipeItem base; + SpiceMsgMainUuid msg; +} UuidPipeItem; + +typedef struct NotifyPipeItem { + PipeItem base; + char *msg; +} NotifyPipeItem; + +typedef struct MultiMediaTimePipeItem { + PipeItem base; + int time; +} MultiMediaTimePipeItem; + +struct MainChannelClient { + RedChannelClient base; + uint32_t connection_id; + uint32_t ping_id; + uint32_t net_test_id; + int net_test_stage; + uint64_t latency; + uint64_t bitrate_per_sec; +#ifdef RED_STATISTICS + SpiceTimer *ping_timer; + int ping_interval; +#endif + int mig_wait_connect; + int mig_connect_ok; + int mig_wait_prev_complete; + int mig_wait_prev_try_seamless; + int init_sent; + int seamless_mig_dst; +}; + +enum NetTestStage { + NET_TEST_STAGE_INVALID, + NET_TEST_STAGE_WARMUP, + NET_TEST_STAGE_LATENCY, + NET_TEST_STAGE_RATE, + NET_TEST_STAGE_COMPLETE, +}; + +static void main_channel_release_pipe_item(RedChannelClient *rcc, + PipeItem *base, int item_pushed); + +int main_channel_is_connected(MainChannel *main_chan) +{ + return red_channel_is_connected(&main_chan->base); +} + +/* + * When the main channel is disconnected, disconnect the entire client. + */ +static void main_channel_client_on_disconnect(RedChannelClient *rcc) +{ + spice_printerr("rcc=%p", rcc); + main_dispatcher_client_disconnect(rcc->client); +} + +RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t connection_id) +{ + RingItem *link; + MainChannelClient *mcc; + + RING_FOREACH(link, &main_chan->base.clients) { + mcc = SPICE_CONTAINEROF(link, MainChannelClient, base.channel_link); + if (mcc->connection_id == connection_id) { + return mcc->base.client; + } + } + return NULL; +} + +static int main_channel_client_push_ping(MainChannelClient *mcc, int size); + +void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate) +{ + if (!mcc || mcc->net_test_id) { + return; + } + if (test_rate) { + if (main_channel_client_push_ping(mcc, NET_TEST_WARMUP_BYTES) + && main_channel_client_push_ping(mcc, 0) + && main_channel_client_push_ping(mcc, NET_TEST_BYTES)) { + mcc->net_test_id = mcc->ping_id - 2; + mcc->net_test_stage = NET_TEST_STAGE_WARMUP; + } + } else { + red_channel_client_start_connectivity_monitoring(&mcc->base, CLIENT_CONNECTIVITY_TIMEOUT); + } +} + +typedef struct MainMouseModeItemInfo { + int current_mode; + int is_client_mouse_allowed; +} MainMouseModeItemInfo; + +static PipeItem *main_mouse_mode_item_new(RedChannelClient *rcc, void *data, int num) +{ + MouseModePipeItem *item = spice_malloc(sizeof(MouseModePipeItem)); + MainMouseModeItemInfo *info = data; + + red_channel_pipe_item_init(rcc->channel, &item->base, + PIPE_ITEM_TYPE_MAIN_MOUSE_MODE); + item->current_mode = info->current_mode; + item->is_client_mouse_allowed = info->is_client_mouse_allowed; + return &item->base; +} + +static PipeItem *main_ping_item_new(MainChannelClient *mcc, int size) +{ + PingPipeItem *item = spice_malloc(sizeof(PingPipeItem)); + + red_channel_pipe_item_init(mcc->base.channel, &item->base, PIPE_ITEM_TYPE_MAIN_PING); + item->size = size; + return &item->base; +} + +static PipeItem *main_agent_tokens_item_new(RedChannelClient *rcc, uint32_t num_tokens) +{ + TokensPipeItem *item = spice_malloc(sizeof(TokensPipeItem)); + + red_channel_pipe_item_init(rcc->channel, &item->base, + PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN); + item->tokens = num_tokens; + return &item->base; +} + +static PipeItem *main_agent_data_item_new(RedChannelClient *rcc, uint8_t* data, size_t len, + spice_marshaller_item_free_func free_data, + void *opaque) +{ + AgentDataPipeItem *item = spice_malloc(sizeof(AgentDataPipeItem)); + + red_channel_pipe_item_init(rcc->channel, &item->base, + PIPE_ITEM_TYPE_MAIN_AGENT_DATA); + item->data = data; + item->len = len; + item->free_data = free_data; + item->opaque = opaque; + return &item->base; +} + +static PipeItem *main_init_item_new(MainChannelClient *mcc, + int connection_id, int display_channels_hint, int current_mouse_mode, + int is_client_mouse_allowed, int multi_media_time, + int ram_hint) +{ + InitPipeItem *item = spice_malloc(sizeof(InitPipeItem)); + + red_channel_pipe_item_init(mcc->base.channel, &item->base, + PIPE_ITEM_TYPE_MAIN_INIT); + item->connection_id = connection_id; + item->display_channels_hint = display_channels_hint; + item->current_mouse_mode = current_mouse_mode; + item->is_client_mouse_allowed = is_client_mouse_allowed; + item->multi_media_time = multi_media_time; + item->ram_hint = ram_hint; + return &item->base; +} + +static PipeItem *main_name_item_new(MainChannelClient *mcc, const char *name) +{ + NamePipeItem *item = spice_malloc(sizeof(NamePipeItem) + strlen(name) + 1); + + red_channel_pipe_item_init(mcc->base.channel, &item->base, + PIPE_ITEM_TYPE_MAIN_NAME); + item->msg.name_len = strlen(name) + 1; + memcpy(&item->msg.name, name, item->msg.name_len); + + return &item->base; +} + +static PipeItem *main_uuid_item_new(MainChannelClient *mcc, const uint8_t uuid[16]) +{ + UuidPipeItem *item = spice_malloc(sizeof(UuidPipeItem)); + + red_channel_pipe_item_init(mcc->base.channel, &item->base, + PIPE_ITEM_TYPE_MAIN_UUID); + memcpy(item->msg.uuid, uuid, sizeof(item->msg.uuid)); + + return &item->base; +} + +static PipeItem *main_notify_item_new(RedChannelClient *rcc, void *data, int num) +{ + NotifyPipeItem *item = spice_malloc(sizeof(NotifyPipeItem)); + const char *msg = data; + + red_channel_pipe_item_init(rcc->channel, &item->base, + PIPE_ITEM_TYPE_MAIN_NOTIFY); + item->msg = spice_strdup(msg); + return &item->base; +} + +static PipeItem *main_multi_media_time_item_new( + RedChannelClient *rcc, void *data, int num) +{ + MultiMediaTimePipeItem *item, *info = data; + + item = spice_malloc(sizeof(MultiMediaTimePipeItem)); + red_channel_pipe_item_init(rcc->channel, &item->base, + PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME); + item->time = info->time; + return &item->base; +} + +static void main_channel_push_channels(MainChannelClient *mcc) +{ + if (red_client_during_migrate_at_target(mcc->base.client)) { + spice_printerr("warning: ignoring unexpected SPICE_MSGC_MAIN_ATTACH_CHANNELS" + "during migration"); + return; + } + red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST); +} + +static void main_channel_marshall_channels(RedChannelClient *rcc, + SpiceMarshaller *m, + PipeItem *item) +{ + SpiceMsgChannels* channels_info; + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_CHANNELS_LIST, item); + channels_info = (SpiceMsgChannels *)spice_malloc(sizeof(SpiceMsgChannels) + + reds_num_of_channels() * sizeof(SpiceChannelId)); + reds_fill_channels(channels_info); + spice_marshall_msg_main_channels_list(m, channels_info); + free(channels_info); +} + +int main_channel_client_push_ping(MainChannelClient *mcc, int size) +{ + PipeItem *item; + + if (mcc == NULL) { + return FALSE; + } + item = main_ping_item_new(mcc, size); + red_channel_client_pipe_add_push(&mcc->base, item); + return TRUE; +} + +static void main_channel_marshall_ping(RedChannelClient *rcc, + SpiceMarshaller *m, + PingPipeItem *item) +{ + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); + struct timespec time_space; + SpiceMsgPing ping; + int size_left = item->size; + + red_channel_client_init_send_data(rcc, SPICE_MSG_PING, &item->base); + ping.id = ++(mcc->ping_id); + clock_gettime(CLOCK_MONOTONIC, &time_space); + ping.timestamp = time_space.tv_sec * 1000000LL + time_space.tv_nsec / 1000LL; + spice_marshall_msg_ping(m, &ping); + + while (size_left > 0) { + int now = MIN(ZERO_BUF_SIZE, size_left); + size_left -= now; + spice_marshaller_add_ref(m, zero_page, now); + } +} + +void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode, + int is_client_mouse_allowed) +{ + MainMouseModeItemInfo info = { + .current_mode=current_mode, + .is_client_mouse_allowed=is_client_mouse_allowed, + }; + + red_channel_pipes_new_add_push(&main_chan->base, + main_mouse_mode_item_new, &info); +} + +static void main_channel_marshall_mouse_mode(RedChannelClient *rcc, + SpiceMarshaller *m, + MouseModePipeItem *item) +{ + SpiceMsgMainMouseMode mouse_mode; + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MOUSE_MODE, &item->base); + mouse_mode.supported_modes = SPICE_MOUSE_MODE_SERVER; + if (item->is_client_mouse_allowed) { + mouse_mode.supported_modes |= SPICE_MOUSE_MODE_CLIENT; + } + mouse_mode.current_mode = item->current_mode; + spice_marshall_msg_main_mouse_mode(m, &mouse_mode); +} + +void main_channel_push_agent_connected(MainChannel *main_chan) +{ + if (red_channel_test_remote_cap(&main_chan->base, SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS)) { + red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS); + } else { + red_channel_pipes_add_empty_msg(&main_chan->base, SPICE_MSG_MAIN_AGENT_CONNECTED); + } +} + +static void main_channel_marshall_agent_connected(SpiceMarshaller *m, + RedChannelClient *rcc, + PipeItem *item) +{ + SpiceMsgMainAgentConnectedTokens connected; + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS, item); + connected.num_tokens = REDS_AGENT_WINDOW_SIZE; + spice_marshall_msg_main_agent_connected_tokens(m, &connected); +} + +void main_channel_push_agent_disconnected(MainChannel *main_chan) +{ + red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED); +} + +static void main_channel_marshall_agent_disconnected(RedChannelClient *rcc, + SpiceMarshaller *m, + PipeItem *item) +{ + SpiceMsgMainAgentDisconnect disconnect; + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DISCONNECTED, item); + disconnect.error_code = SPICE_LINK_ERR_OK; + spice_marshall_msg_main_agent_disconnected(m, &disconnect); +} + +void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens) +{ + PipeItem *item = main_agent_tokens_item_new(&mcc->base, num_tokens); + + red_channel_client_pipe_add_push(&mcc->base, item); +} + +static void main_channel_marshall_tokens(RedChannelClient *rcc, + SpiceMarshaller *m, TokensPipeItem *item) +{ + SpiceMsgMainAgentTokens tokens; + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_TOKEN, &item->base); + tokens.num_tokens = item->tokens; + spice_marshall_msg_main_agent_token(m, &tokens); +} + +void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len, + spice_marshaller_item_free_func free_data, void *opaque) +{ + PipeItem *item; + + item = main_agent_data_item_new(&mcc->base, data, len, free_data, opaque); + red_channel_client_pipe_add_push(&mcc->base, item); +} + +static void main_channel_marshall_agent_data(RedChannelClient *rcc, + SpiceMarshaller *m, + AgentDataPipeItem *item) +{ + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DATA, &item->base); + spice_marshaller_add_ref(m, item->data, item->len); +} + +static void main_channel_push_migrate_data_item(MainChannel *main_chan) +{ + red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA); +} + +static void main_channel_marshall_migrate_data_item(RedChannelClient *rcc, + SpiceMarshaller *m, PipeItem *item) +{ + red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item); + reds_marshall_migrate_data(m); // TODO: from reds split. ugly separation. +} + +static int main_channel_handle_migrate_data(RedChannelClient *rcc, + uint32_t size, void *message) +{ + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); + SpiceMigrateDataHeader *header = (SpiceMigrateDataHeader *)message; + + /* not supported with multi-clients */ + spice_assert(rcc->channel->clients_num == 1); + + if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataMain)) { + spice_printerr("bad message size %u", size); + return FALSE; + } + if (!migration_protocol_validate_header(header, + SPICE_MIGRATE_DATA_MAIN_MAGIC, + SPICE_MIGRATE_DATA_MAIN_VERSION)) { + spice_error("bad header"); + return FALSE; + } + return reds_handle_migrate_data(mcc, (SpiceMigrateDataMain *)(header + 1), size); +} + +void main_channel_push_init(MainChannelClient *mcc, + int display_channels_hint, int current_mouse_mode, + int is_client_mouse_allowed, int multi_media_time, + int ram_hint) +{ + PipeItem *item; + + item = main_init_item_new(mcc, + mcc->connection_id, display_channels_hint, current_mouse_mode, + is_client_mouse_allowed, multi_media_time, ram_hint); + red_channel_client_pipe_add_push(&mcc->base, item); +} + +static void main_channel_marshall_init(RedChannelClient *rcc, + SpiceMarshaller *m, + InitPipeItem *item) +{ + SpiceMsgMainInit init; // TODO - remove this copy, make InitPipeItem reuse SpiceMsgMainInit + + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_INIT, &item->base); + init.session_id = item->connection_id; + init.display_channels_hint = item->display_channels_hint; + init.current_mouse_mode = item->current_mouse_mode; + init.supported_mouse_modes = SPICE_MOUSE_MODE_SERVER; + if (item->is_client_mouse_allowed) { + init.supported_mouse_modes |= SPICE_MOUSE_MODE_CLIENT; + } + init.agent_connected = reds_has_vdagent(); + init.agent_tokens = REDS_AGENT_WINDOW_SIZE; + init.multi_media_time = item->multi_media_time; + init.ram_hint = item->ram_hint; + spice_marshall_msg_main_init(m, &init); +} + +void main_channel_push_name(MainChannelClient *mcc, const char *name) +{ + PipeItem *item; + + if (!red_channel_client_test_remote_cap(&mcc->base, + SPICE_MAIN_CAP_NAME_AND_UUID)) + return; + + item = main_name_item_new(mcc, name); + red_channel_client_pipe_add_push(&mcc->base, item); +} + +void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16]) +{ + PipeItem *item; + + if (!red_channel_client_test_remote_cap(&mcc->base, + SPICE_MAIN_CAP_NAME_AND_UUID)) + return; + + item = main_uuid_item_new(mcc, uuid); + red_channel_client_pipe_add_push(&mcc->base, item); +} + +void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg) +{ + PipeItem *item = main_notify_item_new(&mcc->base, (void *)msg, 1); + red_channel_client_pipe_add_push(&mcc->base, item); +} + +static void main_channel_marshall_notify(RedChannelClient *rcc, + SpiceMarshaller *m, NotifyPipeItem *item) +{ + SpiceMsgNotify notify; + + red_channel_client_init_send_data(rcc, SPICE_MSG_NOTIFY, &item->base); + notify.time_stamp = red_get_monotonic_time(); // TODO - move to main_new_notify_item + notify.severity = SPICE_NOTIFY_SEVERITY_WARN; + notify.visibilty = SPICE_NOTIFY_VISIBILITY_HIGH; + notify.what = SPICE_WARN_GENERAL; + notify.message_len = strlen(item->msg); + spice_marshall_msg_notify(m, ¬ify); + spice_marshaller_add(m, (uint8_t *)item->msg, notify.message_len + 1); +} + +static void main_channel_fill_migrate_dst_info(MainChannel *main_channel, + SpiceMigrationDstInfo *dst_info) +{ + RedsMigSpice *mig_dst = &main_channel->mig_target; + dst_info->port = mig_dst->port; + dst_info->sport = mig_dst->sport; + dst_info->host_size = strlen(mig_dst->host) + 1; + dst_info->host_data = (uint8_t *)mig_dst->host; + if (mig_dst->cert_subject) { + dst_info->cert_subject_size = strlen(mig_dst->cert_subject) + 1; + dst_info->cert_subject_data = (uint8_t *)mig_dst->cert_subject; + } else { + dst_info->cert_subject_size = 0; + dst_info->cert_subject_data = NULL; + } +} + +static void main_channel_marshall_migrate_begin(SpiceMarshaller *m, RedChannelClient *rcc, + PipeItem *item) +{ + SpiceMsgMainMigrationBegin migrate; + MainChannel *main_ch; + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN, item); + main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); + main_channel_fill_migrate_dst_info(main_ch, &migrate.dst_info); + spice_marshall_msg_main_migrate_begin(m, &migrate); +} + +static void main_channel_marshall_migrate_begin_seamless(SpiceMarshaller *m, + RedChannelClient *rcc, + PipeItem *item) +{ + SpiceMsgMainMigrateBeginSeamless migrate_seamless; + MainChannel *main_ch; + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS, item); + main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); + main_channel_fill_migrate_dst_info(main_ch, &migrate_seamless.dst_info); + migrate_seamless.src_mig_version = SPICE_MIGRATION_PROTOCOL_VERSION; + spice_marshall_msg_main_migrate_begin_seamless(m, &migrate_seamless); +} + +void main_channel_push_multi_media_time(MainChannel *main_chan, int time) +{ + MultiMediaTimePipeItem info = { + .time = time, + }; + + red_channel_pipes_new_add_push(&main_chan->base, + main_multi_media_time_item_new, &info); +} + +static void main_channel_fill_mig_target(MainChannel *main_channel, RedsMigSpice *mig_target) +{ + spice_assert(mig_target); + free(main_channel->mig_target.host); + main_channel->mig_target.host = spice_strdup(mig_target->host); + free(main_channel->mig_target.cert_subject); + if (mig_target->cert_subject) { + main_channel->mig_target.cert_subject = spice_strdup(mig_target->cert_subject); + } else { + main_channel->mig_target.cert_subject = NULL; + } + main_channel->mig_target.port = mig_target->port; + main_channel->mig_target.sport = mig_target->sport; +} + +void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target) +{ + main_channel_fill_mig_target(main_chan, mig_target); + red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST); +} + +static void main_channel_marshall_migrate_switch(SpiceMarshaller *m, RedChannelClient *rcc, + PipeItem *item) +{ + SpiceMsgMainMigrationSwitchHost migrate; + MainChannel *main_ch; + + spice_printerr(""); + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST, item); + main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); + migrate.port = main_ch->mig_target.port; + migrate.sport = main_ch->mig_target.sport; + migrate.host_size = strlen(main_ch->mig_target.host) + 1; + migrate.host_data = (uint8_t *)main_ch->mig_target.host; + if (main_ch->mig_target.cert_subject) { + migrate.cert_subject_size = strlen(main_ch->mig_target.cert_subject) + 1; + migrate.cert_subject_data = (uint8_t *)main_ch->mig_target.cert_subject; + } else { + migrate.cert_subject_size = 0; + migrate.cert_subject_data = NULL; + } + spice_marshall_msg_main_migrate_switch_host(m, &migrate); +} + +static void main_channel_marshall_multi_media_time(RedChannelClient *rcc, + SpiceMarshaller *m, + MultiMediaTimePipeItem *item) +{ + SpiceMsgMainMultiMediaTime time_mes; + + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MULTI_MEDIA_TIME, &item->base); + time_mes.time = item->time; + spice_marshall_msg_main_multi_media_time(m, &time_mes); +} + +static void main_channel_send_item(RedChannelClient *rcc, PipeItem *base) +{ + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); + SpiceMarshaller *m = red_channel_client_get_marshaller(rcc); + + /* In semi-seamless migration (dest side), the connection is started from scratch, and + * we ignore any pipe item that arrives before the INIT msg is sent. + * For seamless we don't send INIT, and the connection continues from the same place + * it stopped on the src side. */ + if (!mcc->init_sent && !mcc->seamless_mig_dst && base->type != PIPE_ITEM_TYPE_MAIN_INIT) { + spice_printerr("Init msg for client %p was not sent yet " + "(client is probably during semi-seamless migration). Ignoring msg type %d", + rcc->client, base->type); + main_channel_release_pipe_item(rcc, base, FALSE); + return; + } + switch (base->type) { + case PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST: + main_channel_marshall_channels(rcc, m, base); + break; + case PIPE_ITEM_TYPE_MAIN_PING: + main_channel_marshall_ping(rcc, m, + SPICE_CONTAINEROF(base, PingPipeItem, base)); + break; + case PIPE_ITEM_TYPE_MAIN_MOUSE_MODE: + { + MouseModePipeItem *item = + SPICE_CONTAINEROF(base, MouseModePipeItem, base); + main_channel_marshall_mouse_mode(rcc, m, item); + break; + } + case PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED: + main_channel_marshall_agent_disconnected(rcc, m, base); + break; + case PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN: + main_channel_marshall_tokens(rcc, m, + SPICE_CONTAINEROF(base, TokensPipeItem, base)); + break; + case PIPE_ITEM_TYPE_MAIN_AGENT_DATA: + main_channel_marshall_agent_data(rcc, m, + SPICE_CONTAINEROF(base, AgentDataPipeItem, base)); + break; + case PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA: + main_channel_marshall_migrate_data_item(rcc, m, base); + break; + case PIPE_ITEM_TYPE_MAIN_INIT: + mcc->init_sent = TRUE; + main_channel_marshall_init(rcc, m, + SPICE_CONTAINEROF(base, InitPipeItem, base)); + break; + case PIPE_ITEM_TYPE_MAIN_NOTIFY: + main_channel_marshall_notify(rcc, m, + SPICE_CONTAINEROF(base, NotifyPipeItem, base)); + break; + case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN: + main_channel_marshall_migrate_begin(m, rcc, base); + break; + case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS: + main_channel_marshall_migrate_begin_seamless(m, rcc, base); + break; + case PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME: + main_channel_marshall_multi_media_time(rcc, m, + SPICE_CONTAINEROF(base, MultiMediaTimePipeItem, base)); + break; + case PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST: + main_channel_marshall_migrate_switch(m, rcc, base); + break; + case PIPE_ITEM_TYPE_MAIN_NAME: + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_NAME, base); + spice_marshall_msg_main_name(m, &SPICE_CONTAINEROF(base, NamePipeItem, base)->msg); + break; + case PIPE_ITEM_TYPE_MAIN_UUID: + red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_UUID, base); + spice_marshall_msg_main_uuid(m, &SPICE_CONTAINEROF(base, UuidPipeItem, base)->msg); + break; + case PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS: + main_channel_marshall_agent_connected(m, rcc, base); + break; + default: + break; + }; + red_channel_client_begin_send_message(rcc); +} + +static void main_channel_release_pipe_item(RedChannelClient *rcc, + PipeItem *base, int item_pushed) +{ + switch (base->type) { + case PIPE_ITEM_TYPE_MAIN_AGENT_DATA: { + AgentDataPipeItem *data = (AgentDataPipeItem *)base; + + data->free_data(data->data, data->opaque); + break; + } + case PIPE_ITEM_TYPE_MAIN_NOTIFY: { + NotifyPipeItem *data = (NotifyPipeItem *)base; + free(data->msg); + break; + } + default: + break; + } + free(base); +} + +static void main_channel_client_handle_migrate_connected(MainChannelClient *mcc, + int success, + int seamless) +{ + spice_printerr("client %p connected: %d seamless %d", mcc->base.client, success, seamless); + if (mcc->mig_wait_connect) { + MainChannel *main_channel = SPICE_CONTAINEROF(mcc->base.channel, MainChannel, base); + + mcc->mig_wait_connect = FALSE; + mcc->mig_connect_ok = success; + spice_assert(main_channel->num_clients_mig_wait); + spice_assert(!seamless || main_channel->num_clients_mig_wait == 1); + if (!--main_channel->num_clients_mig_wait) { + reds_on_main_migrate_connected(seamless && success); + } + } else { + if (success) { + spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client); + red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL); + } + } +} + +void main_channel_client_handle_migrate_dst_do_seamless(MainChannelClient *mcc, + uint32_t src_version) +{ + if (reds_on_migrate_dst_set_seamless(mcc, src_version)) { + mcc->seamless_mig_dst = TRUE; + red_channel_client_pipe_add_empty_msg(&mcc->base, + SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK); + } else { + red_channel_client_pipe_add_empty_msg(&mcc->base, + SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK); + } +} + +void main_channel_client_handle_migrate_end(MainChannelClient *mcc) +{ + if (!red_client_during_migrate_at_target(mcc->base.client)) { + spice_printerr("unexpected SPICE_MSGC_MIGRATE_END"); + return; + } + if (!red_channel_client_test_remote_cap(&mcc->base, + SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) { + spice_printerr("unexpected SPICE_MSGC_MIGRATE_END, " + "client does not support semi-seamless migration"); + return; + } + red_client_semi_seamless_migrate_complete(mcc->base.client); +} + +void main_channel_migrate_dst_complete(MainChannelClient *mcc) +{ + if (mcc->mig_wait_prev_complete) { + if (mcc->mig_wait_prev_try_seamless) { + spice_assert(mcc->base.channel->clients_num == 1); + red_channel_client_pipe_add_type(&mcc->base, + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS); + } else { + red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN); + } + mcc->mig_wait_connect = TRUE; + mcc->mig_wait_prev_complete = FALSE; + } +} + +static int main_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type, + void *message) +{ + MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); + + switch (type) { + case SPICE_MSGC_MAIN_AGENT_START: { + SpiceMsgcMainAgentStart *tokens; + + spice_printerr("agent start"); + if (!main_chan) { + return FALSE; + } + tokens = (SpiceMsgcMainAgentStart *)message; + reds_on_main_agent_start(mcc, tokens->num_tokens); + break; + } + case SPICE_MSGC_MAIN_AGENT_DATA: { + reds_on_main_agent_data(mcc, message, size); + break; + } + case SPICE_MSGC_MAIN_AGENT_TOKEN: { + SpiceMsgcMainAgentTokens *tokens; + + tokens = (SpiceMsgcMainAgentTokens *)message; + reds_on_main_agent_tokens(mcc, tokens->num_tokens); + break; + } + case SPICE_MSGC_MAIN_ATTACH_CHANNELS: + main_channel_push_channels(mcc); + break; + case SPICE_MSGC_MAIN_MIGRATE_CONNECTED: + main_channel_client_handle_migrate_connected(mcc, + TRUE /* success */, + FALSE /* seamless */); + break; + case SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS: + main_channel_client_handle_migrate_connected(mcc, + TRUE /* success */, + TRUE /* seamless */); + break; + case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR: + main_channel_client_handle_migrate_connected(mcc, FALSE, FALSE); + break; + case SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS: + main_channel_client_handle_migrate_dst_do_seamless(mcc, + ((SpiceMsgcMainMigrateDstDoSeamless *)message)->src_version); + break; + case SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST: + reds_on_main_mouse_mode_request(message, size); + break; + case SPICE_MSGC_PONG: { + SpiceMsgPing *ping = (SpiceMsgPing *)message; + uint64_t roundtrip; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + roundtrip = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL - ping->timestamp; + + if (ping->id == mcc->net_test_id) { + switch (mcc->net_test_stage) { + case NET_TEST_STAGE_WARMUP: + mcc->net_test_id++; + mcc->net_test_stage = NET_TEST_STAGE_LATENCY; + mcc->latency = roundtrip; + break; + case NET_TEST_STAGE_LATENCY: + mcc->net_test_id++; + mcc->net_test_stage = NET_TEST_STAGE_RATE; + mcc->latency = MIN(mcc->latency, roundtrip); + break; + case NET_TEST_STAGE_RATE: + mcc->net_test_id = 0; + if (roundtrip <= mcc->latency) { + // probably high load on client or server result with incorrect values + spice_printerr("net test: invalid values, latency %" PRIu64 + " roundtrip %" PRIu64 ". assuming high" + " bandwidth", mcc->latency, roundtrip); + mcc->latency = 0; + mcc->net_test_stage = NET_TEST_STAGE_INVALID; + red_channel_client_start_connectivity_monitoring(&mcc->base, + CLIENT_CONNECTIVITY_TIMEOUT); + break; + } + mcc->bitrate_per_sec = (uint64_t)(NET_TEST_BYTES * 8) * 1000000 + / (roundtrip - mcc->latency); + mcc->net_test_stage = NET_TEST_STAGE_COMPLETE; + spice_printerr("net test: latency %f ms, bitrate %"PRIu64" bps (%f Mbps)%s", + (double)mcc->latency / 1000, + mcc->bitrate_per_sec, + (double)mcc->bitrate_per_sec / 1024 / 1024, + main_channel_client_is_low_bandwidth(mcc) ? " LOW BANDWIDTH" : ""); + red_channel_client_start_connectivity_monitoring(&mcc->base, + CLIENT_CONNECTIVITY_TIMEOUT); + break; + default: + spice_printerr("invalid net test stage, ping id %d test id %d stage %d", + ping->id, + mcc->net_test_id, + mcc->net_test_stage); + mcc->net_test_stage = NET_TEST_STAGE_INVALID; + } + break; + } else { + /* + * channel client monitors the connectivity using ping-pong messages + */ + red_channel_client_handle_message(rcc, size, type, message); + } +#ifdef RED_STATISTICS + reds_update_stat_value(roundtrip); +#endif + break; + } + case SPICE_MSGC_DISCONNECTING: + break; + case SPICE_MSGC_MAIN_MIGRATE_END: + main_channel_client_handle_migrate_end(mcc); + break; + default: + return red_channel_client_handle_message(rcc, size, type, message); + } + return TRUE; +} + +static uint8_t *main_channel_alloc_msg_rcv_buf(RedChannelClient *rcc, + uint16_t type, + uint32_t size) +{ + MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); + MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); + + if (type == SPICE_MSGC_MAIN_AGENT_DATA) { + return reds_get_agent_data_buffer(mcc, size); + } else { + return main_chan->recv_buf; + } +} + +static void main_channel_release_msg_rcv_buf(RedChannelClient *rcc, + uint16_t type, + uint32_t size, + uint8_t *msg) +{ + if (type == SPICE_MSGC_MAIN_AGENT_DATA) { + reds_release_agent_data_buffer(msg); + } +} + +static int main_channel_config_socket(RedChannelClient *rcc) +{ + return TRUE; +} + +static void main_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item) +{ +} + +static int main_channel_handle_migrate_flush_mark(RedChannelClient *rcc) +{ + spice_debug(NULL); + main_channel_push_migrate_data_item(SPICE_CONTAINEROF(rcc->channel, + MainChannel, base)); + return TRUE; +} + +#ifdef RED_STATISTICS +static void do_ping_client(MainChannelClient *mcc, + const char *opt, int has_interval, int interval) +{ + spice_printerr(""); + if (!opt) { + main_channel_client_push_ping(mcc, 0); + } else if (!strcmp(opt, "on")) { + if (has_interval && interval > 0) { + mcc->ping_interval = interval * 1000; + } + core->timer_start(mcc->ping_timer, mcc->ping_interval); + } else if (!strcmp(opt, "off")) { + core->timer_cancel(mcc->ping_timer); + } else { + return; + } +} + +static void ping_timer_cb(void *opaque) +{ + MainChannelClient *mcc = opaque; + + if (!red_channel_client_is_connected(&mcc->base)) { + spice_printerr("not connected to peer, ping off"); + core->timer_cancel(mcc->ping_timer); + return; + } + do_ping_client(mcc, NULL, 0, 0); + core->timer_start(mcc->ping_timer, mcc->ping_interval); +} +#endif /* RED_STATISTICS */ + +static MainChannelClient *main_channel_client_create(MainChannel *main_chan, RedClient *client, + RedsStream *stream, uint32_t connection_id, + int num_common_caps, uint32_t *common_caps, + int num_caps, uint32_t *caps) +{ + MainChannelClient *mcc = (MainChannelClient*) + red_channel_client_create(sizeof(MainChannelClient), &main_chan->base, + client, stream, FALSE, num_common_caps, + common_caps, num_caps, caps); + spice_assert(mcc != NULL); + mcc->connection_id = connection_id; + mcc->bitrate_per_sec = ~0; +#ifdef RED_STATISTICS + if (!(mcc->ping_timer = core->timer_add(ping_timer_cb, NULL))) { + spice_error("ping timer create failed"); + } + mcc->ping_interval = PING_INTERVAL; +#endif + return mcc; +} + +MainChannelClient *main_channel_link(MainChannel *channel, RedClient *client, + RedsStream *stream, uint32_t connection_id, int migration, + int num_common_caps, uint32_t *common_caps, int num_caps, + uint32_t *caps) +{ + MainChannelClient *mcc; + + spice_assert(channel); + + // TODO - migration - I removed it from channel creation, now put it + // into usage somewhere (not an issue until we return migration to it's + // former glory) + spice_printerr("add main channel client"); + mcc = main_channel_client_create(channel, client, stream, connection_id, + num_common_caps, common_caps, + num_caps, caps); + return mcc; +} + +int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen) +{ + return main_chan ? getsockname(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1; +} + +int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen) +{ + return main_chan ? getpeername(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1; +} + +// TODO: ? shouldn't it disonnect all clients? or shutdown all main_channels? +void main_channel_close(MainChannel *main_chan) +{ + int socketfd; + + if (main_chan && (socketfd = red_channel_get_first_socket(&main_chan->base)) != -1) { + close(socketfd); + } +} + +int main_channel_client_is_network_info_initialized(MainChannelClient *mcc) +{ + return mcc->net_test_stage == NET_TEST_STAGE_COMPLETE; +} + +int main_channel_client_is_low_bandwidth(MainChannelClient *mcc) +{ + // TODO: configurable? + return mcc->bitrate_per_sec < 10 * 1024 * 1024; +} + +uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc) +{ + return mcc->bitrate_per_sec; +} + +uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc) +{ + return mcc->latency / 1000; +} + +static void main_channel_client_migrate(RedChannelClient *rcc) +{ + reds_on_main_channel_migrate(SPICE_CONTAINEROF(rcc, MainChannelClient, base)); + red_channel_client_default_migrate(rcc); +} + +MainChannel* main_channel_init(void) +{ + RedChannel *channel; + ChannelCbs channel_cbs = { NULL, }; + ClientCbs client_cbs = {NULL, }; + + channel_cbs.config_socket = main_channel_config_socket; + channel_cbs.on_disconnect = main_channel_client_on_disconnect; + channel_cbs.send_item = main_channel_send_item; + channel_cbs.hold_item = main_channel_hold_pipe_item; + channel_cbs.release_item = main_channel_release_pipe_item; + channel_cbs.alloc_recv_buf = main_channel_alloc_msg_rcv_buf; + channel_cbs.release_recv_buf = main_channel_release_msg_rcv_buf; + channel_cbs.handle_migrate_flush_mark = main_channel_handle_migrate_flush_mark; + channel_cbs.handle_migrate_data = main_channel_handle_migrate_data; + + // TODO: set the migration flag of the channel + channel = red_channel_create_parser(sizeof(MainChannel), core, + SPICE_CHANNEL_MAIN, 0, + FALSE, /* handle_acks */ + spice_get_client_channel_parser(SPICE_CHANNEL_MAIN, NULL), + main_channel_handle_parsed, + &channel_cbs, + SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER); + spice_assert(channel); + red_channel_set_cap(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE); + red_channel_set_cap(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE); + + client_cbs.migrate = main_channel_client_migrate; + red_channel_register_client_cbs(channel, &client_cbs); + + return (MainChannel *)channel; +} + +RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc) +{ + spice_assert(mcc); + return &mcc->base; +} + +static int main_channel_connect_semi_seamless(MainChannel *main_channel) +{ + RingItem *client_link; + + RING_FOREACH(client_link, &main_channel->base.clients) { + MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, + base.channel_link); + if (red_channel_client_test_remote_cap(&mcc->base, + SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) { + if (red_client_during_migrate_at_target(mcc->base.client)) { + spice_printerr("client %p: wait till previous migration completes", mcc->base.client); + mcc->mig_wait_prev_complete = TRUE; + mcc->mig_wait_prev_try_seamless = FALSE; + } else { + red_channel_client_pipe_add_type(&mcc->base, + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN); + mcc->mig_wait_connect = TRUE; + } + mcc->mig_connect_ok = FALSE; + main_channel->num_clients_mig_wait++; + } + } + return main_channel->num_clients_mig_wait; +} + +static int main_channel_connect_seamless(MainChannel *main_channel) +{ + RingItem *client_link; + + spice_assert(main_channel->base.clients_num == 1); + + RING_FOREACH(client_link, &main_channel->base.clients) { + MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, + base.channel_link); + spice_assert(red_channel_client_test_remote_cap(&mcc->base, + SPICE_MAIN_CAP_SEAMLESS_MIGRATE)); + if (red_client_during_migrate_at_target(mcc->base.client)) { + spice_printerr("client %p: wait till previous migration completes", mcc->base.client); + mcc->mig_wait_prev_complete = TRUE; + mcc->mig_wait_prev_try_seamless = TRUE; + } else { + red_channel_client_pipe_add_type(&mcc->base, + PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS); + mcc->mig_wait_connect = TRUE; + } + mcc->mig_connect_ok = FALSE; + main_channel->num_clients_mig_wait++; + } + return main_channel->num_clients_mig_wait; +} + +int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target, + int try_seamless) +{ + main_channel_fill_mig_target(main_channel, mig_target); + main_channel->num_clients_mig_wait = 0; + + if (!main_channel_is_connected(main_channel)) { + return 0; + } + + if (!try_seamless) { + return main_channel_connect_semi_seamless(main_channel); + } else { + RingItem *client_item; + MainChannelClient *mcc; + + client_item = ring_get_head(&main_channel->base.clients); + mcc = SPICE_CONTAINEROF(client_item, MainChannelClient, base.channel_link); + + if (!red_channel_client_test_remote_cap(&mcc->base, + SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) { + return main_channel_connect_semi_seamless(main_channel); + } else { + return main_channel_connect_seamless(main_channel); + } + } + +} + +void main_channel_migrate_cancel_wait(MainChannel *main_chan) +{ + RingItem *client_link; + + RING_FOREACH(client_link, &main_chan->base.clients) { + MainChannelClient *mcc; + + mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link); + if (mcc->mig_wait_connect) { + spice_printerr("client %p cancel wait connect", mcc->base.client); + mcc->mig_wait_connect = FALSE; + mcc->mig_connect_ok = FALSE; + } + mcc->mig_wait_prev_complete = FALSE; + } + main_chan->num_clients_mig_wait = 0; +} + +int main_channel_migrate_src_complete(MainChannel *main_chan, int success) +{ + RingItem *client_link; + int semi_seamless_count = 0; + + spice_printerr(""); + + if (ring_is_empty(&main_chan->base.clients)) { + spice_printerr("no peer connected"); + return 0; + } + + RING_FOREACH(client_link, &main_chan->base.clients) { + MainChannelClient *mcc; + int semi_seamless_support; + + mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link); + semi_seamless_support = red_channel_client_test_remote_cap(&mcc->base, + SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE); + if (semi_seamless_support && mcc->mig_connect_ok) { + if (success) { + spice_printerr("client %p MIGRATE_END", mcc->base.client); + red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_END); + semi_seamless_count++; + } else { + spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client); + red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL); + } + } else { + if (success) { + spice_printerr("client %p SWITCH_HOST", mcc->base.client); + red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST); + } + } + mcc->mig_connect_ok = FALSE; + mcc->mig_wait_connect = FALSE; + } + return semi_seamless_count; +} diff --git a/server/main-channel.h b/server/main-channel.h new file mode 100644 index 0000000..9bd20f1 --- /dev/null +++ b/server/main-channel.h @@ -0,0 +1,103 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __MAIN_CHANNEL_H__ +#define __MAIN_CHANNEL_H__ + +#include <stdint.h> +#include <spice/vd_agent.h> +#include "common/marshaller.h" +#include "red_channel.h" + +// TODO: Defines used to calculate receive buffer size, and also by reds.c +// other options: is to make a reds_main_consts.h, to duplicate defines. +#define REDS_AGENT_WINDOW_SIZE 10 +#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1 + +// approximate max receive message size for main channel +#define MAIN_CHANNEL_RECEIVE_BUF_SIZE \ + (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * SPICE_AGENT_MAX_DATA_SIZE) + +struct RedsMigSpice { + char *host; + char *cert_subject; + int port; + int sport; +}; +typedef struct RedsMigSpice RedsMigSpice; + +typedef struct MainChannel { + RedChannel base; + uint8_t recv_buf[MAIN_CHANNEL_RECEIVE_BUF_SIZE]; + RedsMigSpice mig_target; // TODO: add refs and release (afrer all clients completed migration in one way or the other?) + int num_clients_mig_wait; +} MainChannel; + + +MainChannel *main_channel_init(void); +RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t link_id); +/* This is a 'clone' from the reds.h Channel.link callback to allow passing link_id */ +MainChannelClient *main_channel_link(MainChannel *, RedClient *client, + RedsStream *stream, uint32_t link_id, int migration, int num_common_caps, + uint32_t *common_caps, int num_caps, uint32_t *caps); +void main_channel_close(MainChannel *main_chan); // not destroy, just socket close +void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode, int is_client_mouse_allowed); +void main_channel_push_agent_connected(MainChannel *main_chan); +void main_channel_push_agent_disconnected(MainChannel *main_chan); +void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens); +void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len, + spice_marshaller_item_free_func free_data, void *opaque); +void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate); +// TODO: huge. Consider making a reds_* interface for these functions +// and calling from main. +void main_channel_push_init(MainChannelClient *mcc, int display_channels_hint, + int current_mouse_mode, int is_client_mouse_allowed, int multi_media_time, + int ram_hint); +void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg); +void main_channel_push_multi_media_time(MainChannel *main_chan, int time); +int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen); +int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen); + +/* + * return TRUE if network test had been completed successfully. + * If FALSE, bitrate_per_sec is set to MAX_UINT64 and the roundtrip is set to 0 + */ +int main_channel_client_is_network_info_initialized(MainChannelClient *mcc); +int main_channel_client_is_low_bandwidth(MainChannelClient *mcc); +uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc); +uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc); + +int main_channel_is_connected(MainChannel *main_chan); +RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc); + +/* switch host migration */ +void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target); + +/* semi seamless migration */ + +/* returns the number of clients that we are waiting for their connection. + * try_seamless = 'true' when the seamless-migration=on in qemu command line */ +int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target, + int try_seamless); +void main_channel_migrate_cancel_wait(MainChannel *main_chan); +/* returns the number of clients for which SPICE_MSG_MAIN_MIGRATE_END was sent*/ +int main_channel_migrate_src_complete(MainChannel *main_chan, int success); +void main_channel_migrate_dst_complete(MainChannelClient *mcc); +void main_channel_push_name(MainChannelClient *mcc, const char *name); +void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16]); + +#endif diff --git a/server/main-dispatcher.c b/server/main-dispatcher.c new file mode 100644 index 0000000..eb7cee6 --- /dev/null +++ b/server/main-dispatcher.c @@ -0,0 +1,217 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#include <config.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> + +#include "red_common.h" +#include "dispatcher.h" +#include "main-dispatcher.h" +#include "red_channel.h" +#include "reds.h" + +/* + * Main Dispatcher + * =============== + * + * Communication channel between any non main thread and the main thread. + * + * The main thread is that from which spice_server_init is called. + * + * Messages are single sized, sent from the non-main thread to the main-thread. + * No acknowledge is sent back. This prevents a possible deadlock with the main + * thread already waiting on a response for the existing red_dispatcher used + * by the worker thread. + * + * All events have three functions: + * main_dispatcher_<event_name> - non static, public function + * main_dispatcher_self_<event_name> - handler for main thread + * main_dispatcher_handle_<event_name> - handler for callback from main thread + * seperate from self because it may send an ack or do other work in the future. + */ + +typedef struct { + Dispatcher base; + SpiceCoreInterface *core; +} MainDispatcher; + +MainDispatcher main_dispatcher; + +enum { + MAIN_DISPATCHER_CHANNEL_EVENT = 0, + MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE, + MAIN_DISPATCHER_SET_MM_TIME_LATENCY, + MAIN_DISPATCHER_CLIENT_DISCONNECT, + + MAIN_DISPATCHER_NUM_MESSAGES +}; + +typedef struct MainDispatcherChannelEventMessage { + int event; + SpiceChannelEventInfo *info; +} MainDispatcherChannelEventMessage; + +typedef struct MainDispatcherMigrateSeamlessDstCompleteMessage { + RedClient *client; +} MainDispatcherMigrateSeamlessDstCompleteMessage; + +typedef struct MainDispatcherMmTimeLatencyMessage { + RedClient *client; + uint32_t latency; +} MainDispatcherMmTimeLatencyMessage; + +typedef struct MainDispatcherClientDisconnectMessage { + RedClient *client; +} MainDispatcherClientDisconnectMessage; + +/* channel_event - calls core->channel_event, must be done in main thread */ +static void main_dispatcher_self_handle_channel_event( + int event, + SpiceChannelEventInfo *info) +{ + reds_handle_channel_event(event, info); +} + +static void main_dispatcher_handle_channel_event(void *opaque, + void *payload) +{ + MainDispatcherChannelEventMessage *channel_event = payload; + + main_dispatcher_self_handle_channel_event(channel_event->event, + channel_event->info); +} + +void main_dispatcher_channel_event(int event, SpiceChannelEventInfo *info) +{ + MainDispatcherChannelEventMessage msg = {0,}; + + if (pthread_self() == main_dispatcher.base.self) { + main_dispatcher_self_handle_channel_event(event, info); + return; + } + msg.event = event; + msg.info = info; + dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_CHANNEL_EVENT, + &msg); +} + + +static void main_dispatcher_handle_migrate_complete(void *opaque, + void *payload) +{ + MainDispatcherMigrateSeamlessDstCompleteMessage *mig_complete = payload; + + reds_on_client_seamless_migrate_complete(mig_complete->client); + red_client_unref(mig_complete->client); +} + +static void main_dispatcher_handle_mm_time_latency(void *opaque, + void *payload) +{ + MainDispatcherMmTimeLatencyMessage *msg = payload; + reds_set_client_mm_time_latency(msg->client, msg->latency); + red_client_unref(msg->client); +} + +static void main_dispatcher_handle_client_disconnect(void *opaque, + void *payload) +{ + MainDispatcherClientDisconnectMessage *msg = payload; + + spice_debug("client=%p", msg->client); + reds_client_disconnect(msg->client); + red_client_unref(msg->client); +} + +void main_dispatcher_seamless_migrate_dst_complete(RedClient *client) +{ + MainDispatcherMigrateSeamlessDstCompleteMessage msg; + + if (pthread_self() == main_dispatcher.base.self) { + reds_on_client_seamless_migrate_complete(client); + return; + } + + msg.client = red_client_ref(client); + dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE, + &msg); +} + +void main_dispatcher_set_mm_time_latency(RedClient *client, uint32_t latency) +{ + MainDispatcherMmTimeLatencyMessage msg; + + if (pthread_self() == main_dispatcher.base.self) { + reds_set_client_mm_time_latency(client, latency); + return; + } + + msg.client = red_client_ref(client); + msg.latency = latency; + dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_SET_MM_TIME_LATENCY, + &msg); +} + +void main_dispatcher_client_disconnect(RedClient *client) +{ + MainDispatcherClientDisconnectMessage msg; + + if (!client->disconnecting) { + spice_debug("client %p", client); + msg.client = red_client_ref(client); + dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_CLIENT_DISCONNECT, + &msg); + } else { + spice_debug("client %p already during disconnection", client); + } +} + +static void dispatcher_handle_read(int fd, int event, void *opaque) +{ + Dispatcher *dispatcher = opaque; + + dispatcher_handle_recv_read(dispatcher); +} + +/* + * FIXME: + * Reds routines shouldn't be exposed. Instead reds.c should register the callbacks, + * and the corresponding operations should be made only via main_dispatcher. + */ +void main_dispatcher_init(SpiceCoreInterface *core) +{ + memset(&main_dispatcher, 0, sizeof(main_dispatcher)); + main_dispatcher.core = core; + dispatcher_init(&main_dispatcher.base, MAIN_DISPATCHER_NUM_MESSAGES, &main_dispatcher.base); + core->watch_add(main_dispatcher.base.recv_fd, SPICE_WATCH_EVENT_READ, + dispatcher_handle_read, &main_dispatcher.base); + dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_CHANNEL_EVENT, + main_dispatcher_handle_channel_event, + sizeof(MainDispatcherChannelEventMessage), 0 /* no ack */); + dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE, + main_dispatcher_handle_migrate_complete, + sizeof(MainDispatcherMigrateSeamlessDstCompleteMessage), 0 /* no ack */); + dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_SET_MM_TIME_LATENCY, + main_dispatcher_handle_mm_time_latency, + sizeof(MainDispatcherMmTimeLatencyMessage), 0 /* no ack */); + dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_CLIENT_DISCONNECT, + main_dispatcher_handle_client_disconnect, + sizeof(MainDispatcherClientDisconnectMessage), 0 /* no ack */); +} diff --git a/server/main-dispatcher.h b/server/main-dispatcher.h new file mode 100644 index 0000000..af40093 --- /dev/null +++ b/server/main-dispatcher.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef MAIN_DISPATCHER_H +#define MAIN_DISPATCHER_H + +#include <spice.h> +#include "red_channel.h" + +void main_dispatcher_channel_event(int event, SpiceChannelEventInfo *info); +void main_dispatcher_seamless_migrate_dst_complete(RedClient *client); +void main_dispatcher_set_mm_time_latency(RedClient *client, uint32_t latency); +/* + * Disconnecting the client is always executed asynchronously, + * in order to protect from expired references in the routines + * that triggered the client destruction. + */ +void main_dispatcher_client_disconnect(RedClient *client); + +void main_dispatcher_init(SpiceCoreInterface *core); + +#endif //MAIN_DISPATCHER_H diff --git a/server/main_channel.c b/server/main_channel.c deleted file mode 100644 index 1af6baa..0000000 --- a/server/main_channel.c +++ /dev/null @@ -1,1345 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <inttypes.h> -#include <stdint.h> -#include <stdio.h> -#include <unistd.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <arpa/inet.h> -#include <netdb.h> -#include <limits.h> -#include <time.h> -#include <pthread.h> -#include <sys/mman.h> -#include <fcntl.h> -#include <errno.h> -#include <ctype.h> - -#include "common/generated_server_marshallers.h" -#include "common/messages.h" -#include "common/ring.h" - -#include "demarshallers.h" -#include "main_channel.h" -#include "red_channel.h" -#include "red_common.h" -#include "reds.h" -#include "migration_protocol.h" -#include "main_dispatcher.h" -#include "utils.h" - -#define ZERO_BUF_SIZE 4096 - -#define NET_TEST_WARMUP_BYTES 0 -#define NET_TEST_BYTES (1024 * 250) - -#define PING_INTERVAL (1000 * 10) - -#define CLIENT_CONNECTIVITY_TIMEOUT (30*1000) // 30 seconds - -static uint8_t zero_page[ZERO_BUF_SIZE] = {0}; - -enum { - PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST = PIPE_ITEM_TYPE_CHANNEL_BASE, - PIPE_ITEM_TYPE_MAIN_PING, - PIPE_ITEM_TYPE_MAIN_MOUSE_MODE, - PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED, - PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN, - PIPE_ITEM_TYPE_MAIN_AGENT_DATA, - PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA, - PIPE_ITEM_TYPE_MAIN_INIT, - PIPE_ITEM_TYPE_MAIN_NOTIFY, - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN, - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS, - PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST, - PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME, - PIPE_ITEM_TYPE_MAIN_NAME, - PIPE_ITEM_TYPE_MAIN_UUID, - PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS, -}; - -typedef struct RefsPipeItem { - PipeItem base; - int *refs; -} RefsPipeItem; - -typedef struct PingPipeItem { - PipeItem base; - int size; -} PingPipeItem; - -typedef struct MouseModePipeItem { - PipeItem base; - int current_mode; - int is_client_mouse_allowed; -} MouseModePipeItem; - -typedef struct TokensPipeItem { - PipeItem base; - int tokens; -} TokensPipeItem; - -typedef struct AgentDataPipeItem { - PipeItem base; - uint8_t* data; - size_t len; - spice_marshaller_item_free_func free_data; - void *opaque; -} AgentDataPipeItem; - -typedef struct InitPipeItem { - PipeItem base; - int connection_id; - int display_channels_hint; - int current_mouse_mode; - int is_client_mouse_allowed; - int multi_media_time; - int ram_hint; -} InitPipeItem; - -typedef struct NamePipeItem { - PipeItem base; - SpiceMsgMainName msg; -} NamePipeItem; - -typedef struct UuidPipeItem { - PipeItem base; - SpiceMsgMainUuid msg; -} UuidPipeItem; - -typedef struct NotifyPipeItem { - PipeItem base; - char *msg; -} NotifyPipeItem; - -typedef struct MultiMediaTimePipeItem { - PipeItem base; - int time; -} MultiMediaTimePipeItem; - -struct MainChannelClient { - RedChannelClient base; - uint32_t connection_id; - uint32_t ping_id; - uint32_t net_test_id; - int net_test_stage; - uint64_t latency; - uint64_t bitrate_per_sec; -#ifdef RED_STATISTICS - SpiceTimer *ping_timer; - int ping_interval; -#endif - int mig_wait_connect; - int mig_connect_ok; - int mig_wait_prev_complete; - int mig_wait_prev_try_seamless; - int init_sent; - int seamless_mig_dst; -}; - -enum NetTestStage { - NET_TEST_STAGE_INVALID, - NET_TEST_STAGE_WARMUP, - NET_TEST_STAGE_LATENCY, - NET_TEST_STAGE_RATE, - NET_TEST_STAGE_COMPLETE, -}; - -static void main_channel_release_pipe_item(RedChannelClient *rcc, - PipeItem *base, int item_pushed); - -int main_channel_is_connected(MainChannel *main_chan) -{ - return red_channel_is_connected(&main_chan->base); -} - -/* - * When the main channel is disconnected, disconnect the entire client. - */ -static void main_channel_client_on_disconnect(RedChannelClient *rcc) -{ - spice_printerr("rcc=%p", rcc); - main_dispatcher_client_disconnect(rcc->client); -} - -RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t connection_id) -{ - RingItem *link; - MainChannelClient *mcc; - - RING_FOREACH(link, &main_chan->base.clients) { - mcc = SPICE_CONTAINEROF(link, MainChannelClient, base.channel_link); - if (mcc->connection_id == connection_id) { - return mcc->base.client; - } - } - return NULL; -} - -static int main_channel_client_push_ping(MainChannelClient *mcc, int size); - -void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate) -{ - if (!mcc || mcc->net_test_id) { - return; - } - if (test_rate) { - if (main_channel_client_push_ping(mcc, NET_TEST_WARMUP_BYTES) - && main_channel_client_push_ping(mcc, 0) - && main_channel_client_push_ping(mcc, NET_TEST_BYTES)) { - mcc->net_test_id = mcc->ping_id - 2; - mcc->net_test_stage = NET_TEST_STAGE_WARMUP; - } - } else { - red_channel_client_start_connectivity_monitoring(&mcc->base, CLIENT_CONNECTIVITY_TIMEOUT); - } -} - -typedef struct MainMouseModeItemInfo { - int current_mode; - int is_client_mouse_allowed; -} MainMouseModeItemInfo; - -static PipeItem *main_mouse_mode_item_new(RedChannelClient *rcc, void *data, int num) -{ - MouseModePipeItem *item = spice_malloc(sizeof(MouseModePipeItem)); - MainMouseModeItemInfo *info = data; - - red_channel_pipe_item_init(rcc->channel, &item->base, - PIPE_ITEM_TYPE_MAIN_MOUSE_MODE); - item->current_mode = info->current_mode; - item->is_client_mouse_allowed = info->is_client_mouse_allowed; - return &item->base; -} - -static PipeItem *main_ping_item_new(MainChannelClient *mcc, int size) -{ - PingPipeItem *item = spice_malloc(sizeof(PingPipeItem)); - - red_channel_pipe_item_init(mcc->base.channel, &item->base, PIPE_ITEM_TYPE_MAIN_PING); - item->size = size; - return &item->base; -} - -static PipeItem *main_agent_tokens_item_new(RedChannelClient *rcc, uint32_t num_tokens) -{ - TokensPipeItem *item = spice_malloc(sizeof(TokensPipeItem)); - - red_channel_pipe_item_init(rcc->channel, &item->base, - PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN); - item->tokens = num_tokens; - return &item->base; -} - -static PipeItem *main_agent_data_item_new(RedChannelClient *rcc, uint8_t* data, size_t len, - spice_marshaller_item_free_func free_data, - void *opaque) -{ - AgentDataPipeItem *item = spice_malloc(sizeof(AgentDataPipeItem)); - - red_channel_pipe_item_init(rcc->channel, &item->base, - PIPE_ITEM_TYPE_MAIN_AGENT_DATA); - item->data = data; - item->len = len; - item->free_data = free_data; - item->opaque = opaque; - return &item->base; -} - -static PipeItem *main_init_item_new(MainChannelClient *mcc, - int connection_id, int display_channels_hint, int current_mouse_mode, - int is_client_mouse_allowed, int multi_media_time, - int ram_hint) -{ - InitPipeItem *item = spice_malloc(sizeof(InitPipeItem)); - - red_channel_pipe_item_init(mcc->base.channel, &item->base, - PIPE_ITEM_TYPE_MAIN_INIT); - item->connection_id = connection_id; - item->display_channels_hint = display_channels_hint; - item->current_mouse_mode = current_mouse_mode; - item->is_client_mouse_allowed = is_client_mouse_allowed; - item->multi_media_time = multi_media_time; - item->ram_hint = ram_hint; - return &item->base; -} - -static PipeItem *main_name_item_new(MainChannelClient *mcc, const char *name) -{ - NamePipeItem *item = spice_malloc(sizeof(NamePipeItem) + strlen(name) + 1); - - red_channel_pipe_item_init(mcc->base.channel, &item->base, - PIPE_ITEM_TYPE_MAIN_NAME); - item->msg.name_len = strlen(name) + 1; - memcpy(&item->msg.name, name, item->msg.name_len); - - return &item->base; -} - -static PipeItem *main_uuid_item_new(MainChannelClient *mcc, const uint8_t uuid[16]) -{ - UuidPipeItem *item = spice_malloc(sizeof(UuidPipeItem)); - - red_channel_pipe_item_init(mcc->base.channel, &item->base, - PIPE_ITEM_TYPE_MAIN_UUID); - memcpy(item->msg.uuid, uuid, sizeof(item->msg.uuid)); - - return &item->base; -} - -static PipeItem *main_notify_item_new(RedChannelClient *rcc, void *data, int num) -{ - NotifyPipeItem *item = spice_malloc(sizeof(NotifyPipeItem)); - const char *msg = data; - - red_channel_pipe_item_init(rcc->channel, &item->base, - PIPE_ITEM_TYPE_MAIN_NOTIFY); - item->msg = spice_strdup(msg); - return &item->base; -} - -static PipeItem *main_multi_media_time_item_new( - RedChannelClient *rcc, void *data, int num) -{ - MultiMediaTimePipeItem *item, *info = data; - - item = spice_malloc(sizeof(MultiMediaTimePipeItem)); - red_channel_pipe_item_init(rcc->channel, &item->base, - PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME); - item->time = info->time; - return &item->base; -} - -static void main_channel_push_channels(MainChannelClient *mcc) -{ - if (red_client_during_migrate_at_target(mcc->base.client)) { - spice_printerr("warning: ignoring unexpected SPICE_MSGC_MAIN_ATTACH_CHANNELS" - "during migration"); - return; - } - red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST); -} - -static void main_channel_marshall_channels(RedChannelClient *rcc, - SpiceMarshaller *m, - PipeItem *item) -{ - SpiceMsgChannels* channels_info; - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_CHANNELS_LIST, item); - channels_info = (SpiceMsgChannels *)spice_malloc(sizeof(SpiceMsgChannels) - + reds_num_of_channels() * sizeof(SpiceChannelId)); - reds_fill_channels(channels_info); - spice_marshall_msg_main_channels_list(m, channels_info); - free(channels_info); -} - -int main_channel_client_push_ping(MainChannelClient *mcc, int size) -{ - PipeItem *item; - - if (mcc == NULL) { - return FALSE; - } - item = main_ping_item_new(mcc, size); - red_channel_client_pipe_add_push(&mcc->base, item); - return TRUE; -} - -static void main_channel_marshall_ping(RedChannelClient *rcc, - SpiceMarshaller *m, - PingPipeItem *item) -{ - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); - struct timespec time_space; - SpiceMsgPing ping; - int size_left = item->size; - - red_channel_client_init_send_data(rcc, SPICE_MSG_PING, &item->base); - ping.id = ++(mcc->ping_id); - clock_gettime(CLOCK_MONOTONIC, &time_space); - ping.timestamp = time_space.tv_sec * 1000000LL + time_space.tv_nsec / 1000LL; - spice_marshall_msg_ping(m, &ping); - - while (size_left > 0) { - int now = MIN(ZERO_BUF_SIZE, size_left); - size_left -= now; - spice_marshaller_add_ref(m, zero_page, now); - } -} - -void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode, - int is_client_mouse_allowed) -{ - MainMouseModeItemInfo info = { - .current_mode=current_mode, - .is_client_mouse_allowed=is_client_mouse_allowed, - }; - - red_channel_pipes_new_add_push(&main_chan->base, - main_mouse_mode_item_new, &info); -} - -static void main_channel_marshall_mouse_mode(RedChannelClient *rcc, - SpiceMarshaller *m, - MouseModePipeItem *item) -{ - SpiceMsgMainMouseMode mouse_mode; - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MOUSE_MODE, &item->base); - mouse_mode.supported_modes = SPICE_MOUSE_MODE_SERVER; - if (item->is_client_mouse_allowed) { - mouse_mode.supported_modes |= SPICE_MOUSE_MODE_CLIENT; - } - mouse_mode.current_mode = item->current_mode; - spice_marshall_msg_main_mouse_mode(m, &mouse_mode); -} - -void main_channel_push_agent_connected(MainChannel *main_chan) -{ - if (red_channel_test_remote_cap(&main_chan->base, SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS)) { - red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS); - } else { - red_channel_pipes_add_empty_msg(&main_chan->base, SPICE_MSG_MAIN_AGENT_CONNECTED); - } -} - -static void main_channel_marshall_agent_connected(SpiceMarshaller *m, - RedChannelClient *rcc, - PipeItem *item) -{ - SpiceMsgMainAgentConnectedTokens connected; - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS, item); - connected.num_tokens = REDS_AGENT_WINDOW_SIZE; - spice_marshall_msg_main_agent_connected_tokens(m, &connected); -} - -void main_channel_push_agent_disconnected(MainChannel *main_chan) -{ - red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED); -} - -static void main_channel_marshall_agent_disconnected(RedChannelClient *rcc, - SpiceMarshaller *m, - PipeItem *item) -{ - SpiceMsgMainAgentDisconnect disconnect; - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DISCONNECTED, item); - disconnect.error_code = SPICE_LINK_ERR_OK; - spice_marshall_msg_main_agent_disconnected(m, &disconnect); -} - -void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens) -{ - PipeItem *item = main_agent_tokens_item_new(&mcc->base, num_tokens); - - red_channel_client_pipe_add_push(&mcc->base, item); -} - -static void main_channel_marshall_tokens(RedChannelClient *rcc, - SpiceMarshaller *m, TokensPipeItem *item) -{ - SpiceMsgMainAgentTokens tokens; - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_TOKEN, &item->base); - tokens.num_tokens = item->tokens; - spice_marshall_msg_main_agent_token(m, &tokens); -} - -void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len, - spice_marshaller_item_free_func free_data, void *opaque) -{ - PipeItem *item; - - item = main_agent_data_item_new(&mcc->base, data, len, free_data, opaque); - red_channel_client_pipe_add_push(&mcc->base, item); -} - -static void main_channel_marshall_agent_data(RedChannelClient *rcc, - SpiceMarshaller *m, - AgentDataPipeItem *item) -{ - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DATA, &item->base); - spice_marshaller_add_ref(m, item->data, item->len); -} - -static void main_channel_push_migrate_data_item(MainChannel *main_chan) -{ - red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA); -} - -static void main_channel_marshall_migrate_data_item(RedChannelClient *rcc, - SpiceMarshaller *m, PipeItem *item) -{ - red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item); - reds_marshall_migrate_data(m); // TODO: from reds split. ugly separation. -} - -static int main_channel_handle_migrate_data(RedChannelClient *rcc, - uint32_t size, void *message) -{ - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); - SpiceMigrateDataHeader *header = (SpiceMigrateDataHeader *)message; - - /* not supported with multi-clients */ - spice_assert(rcc->channel->clients_num == 1); - - if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataMain)) { - spice_printerr("bad message size %u", size); - return FALSE; - } - if (!migration_protocol_validate_header(header, - SPICE_MIGRATE_DATA_MAIN_MAGIC, - SPICE_MIGRATE_DATA_MAIN_VERSION)) { - spice_error("bad header"); - return FALSE; - } - return reds_handle_migrate_data(mcc, (SpiceMigrateDataMain *)(header + 1), size); -} - -void main_channel_push_init(MainChannelClient *mcc, - int display_channels_hint, int current_mouse_mode, - int is_client_mouse_allowed, int multi_media_time, - int ram_hint) -{ - PipeItem *item; - - item = main_init_item_new(mcc, - mcc->connection_id, display_channels_hint, current_mouse_mode, - is_client_mouse_allowed, multi_media_time, ram_hint); - red_channel_client_pipe_add_push(&mcc->base, item); -} - -static void main_channel_marshall_init(RedChannelClient *rcc, - SpiceMarshaller *m, - InitPipeItem *item) -{ - SpiceMsgMainInit init; // TODO - remove this copy, make InitPipeItem reuse SpiceMsgMainInit - - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_INIT, &item->base); - init.session_id = item->connection_id; - init.display_channels_hint = item->display_channels_hint; - init.current_mouse_mode = item->current_mouse_mode; - init.supported_mouse_modes = SPICE_MOUSE_MODE_SERVER; - if (item->is_client_mouse_allowed) { - init.supported_mouse_modes |= SPICE_MOUSE_MODE_CLIENT; - } - init.agent_connected = reds_has_vdagent(); - init.agent_tokens = REDS_AGENT_WINDOW_SIZE; - init.multi_media_time = item->multi_media_time; - init.ram_hint = item->ram_hint; - spice_marshall_msg_main_init(m, &init); -} - -void main_channel_push_name(MainChannelClient *mcc, const char *name) -{ - PipeItem *item; - - if (!red_channel_client_test_remote_cap(&mcc->base, - SPICE_MAIN_CAP_NAME_AND_UUID)) - return; - - item = main_name_item_new(mcc, name); - red_channel_client_pipe_add_push(&mcc->base, item); -} - -void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16]) -{ - PipeItem *item; - - if (!red_channel_client_test_remote_cap(&mcc->base, - SPICE_MAIN_CAP_NAME_AND_UUID)) - return; - - item = main_uuid_item_new(mcc, uuid); - red_channel_client_pipe_add_push(&mcc->base, item); -} - -void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg) -{ - PipeItem *item = main_notify_item_new(&mcc->base, (void *)msg, 1); - red_channel_client_pipe_add_push(&mcc->base, item); -} - -static void main_channel_marshall_notify(RedChannelClient *rcc, - SpiceMarshaller *m, NotifyPipeItem *item) -{ - SpiceMsgNotify notify; - - red_channel_client_init_send_data(rcc, SPICE_MSG_NOTIFY, &item->base); - notify.time_stamp = red_get_monotonic_time(); // TODO - move to main_new_notify_item - notify.severity = SPICE_NOTIFY_SEVERITY_WARN; - notify.visibilty = SPICE_NOTIFY_VISIBILITY_HIGH; - notify.what = SPICE_WARN_GENERAL; - notify.message_len = strlen(item->msg); - spice_marshall_msg_notify(m, ¬ify); - spice_marshaller_add(m, (uint8_t *)item->msg, notify.message_len + 1); -} - -static void main_channel_fill_migrate_dst_info(MainChannel *main_channel, - SpiceMigrationDstInfo *dst_info) -{ - RedsMigSpice *mig_dst = &main_channel->mig_target; - dst_info->port = mig_dst->port; - dst_info->sport = mig_dst->sport; - dst_info->host_size = strlen(mig_dst->host) + 1; - dst_info->host_data = (uint8_t *)mig_dst->host; - if (mig_dst->cert_subject) { - dst_info->cert_subject_size = strlen(mig_dst->cert_subject) + 1; - dst_info->cert_subject_data = (uint8_t *)mig_dst->cert_subject; - } else { - dst_info->cert_subject_size = 0; - dst_info->cert_subject_data = NULL; - } -} - -static void main_channel_marshall_migrate_begin(SpiceMarshaller *m, RedChannelClient *rcc, - PipeItem *item) -{ - SpiceMsgMainMigrationBegin migrate; - MainChannel *main_ch; - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN, item); - main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); - main_channel_fill_migrate_dst_info(main_ch, &migrate.dst_info); - spice_marshall_msg_main_migrate_begin(m, &migrate); -} - -static void main_channel_marshall_migrate_begin_seamless(SpiceMarshaller *m, - RedChannelClient *rcc, - PipeItem *item) -{ - SpiceMsgMainMigrateBeginSeamless migrate_seamless; - MainChannel *main_ch; - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS, item); - main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); - main_channel_fill_migrate_dst_info(main_ch, &migrate_seamless.dst_info); - migrate_seamless.src_mig_version = SPICE_MIGRATION_PROTOCOL_VERSION; - spice_marshall_msg_main_migrate_begin_seamless(m, &migrate_seamless); -} - -void main_channel_push_multi_media_time(MainChannel *main_chan, int time) -{ - MultiMediaTimePipeItem info = { - .time = time, - }; - - red_channel_pipes_new_add_push(&main_chan->base, - main_multi_media_time_item_new, &info); -} - -static void main_channel_fill_mig_target(MainChannel *main_channel, RedsMigSpice *mig_target) -{ - spice_assert(mig_target); - free(main_channel->mig_target.host); - main_channel->mig_target.host = spice_strdup(mig_target->host); - free(main_channel->mig_target.cert_subject); - if (mig_target->cert_subject) { - main_channel->mig_target.cert_subject = spice_strdup(mig_target->cert_subject); - } else { - main_channel->mig_target.cert_subject = NULL; - } - main_channel->mig_target.port = mig_target->port; - main_channel->mig_target.sport = mig_target->sport; -} - -void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target) -{ - main_channel_fill_mig_target(main_chan, mig_target); - red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST); -} - -static void main_channel_marshall_migrate_switch(SpiceMarshaller *m, RedChannelClient *rcc, - PipeItem *item) -{ - SpiceMsgMainMigrationSwitchHost migrate; - MainChannel *main_ch; - - spice_printerr(""); - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST, item); - main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); - migrate.port = main_ch->mig_target.port; - migrate.sport = main_ch->mig_target.sport; - migrate.host_size = strlen(main_ch->mig_target.host) + 1; - migrate.host_data = (uint8_t *)main_ch->mig_target.host; - if (main_ch->mig_target.cert_subject) { - migrate.cert_subject_size = strlen(main_ch->mig_target.cert_subject) + 1; - migrate.cert_subject_data = (uint8_t *)main_ch->mig_target.cert_subject; - } else { - migrate.cert_subject_size = 0; - migrate.cert_subject_data = NULL; - } - spice_marshall_msg_main_migrate_switch_host(m, &migrate); -} - -static void main_channel_marshall_multi_media_time(RedChannelClient *rcc, - SpiceMarshaller *m, - MultiMediaTimePipeItem *item) -{ - SpiceMsgMainMultiMediaTime time_mes; - - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MULTI_MEDIA_TIME, &item->base); - time_mes.time = item->time; - spice_marshall_msg_main_multi_media_time(m, &time_mes); -} - -static void main_channel_send_item(RedChannelClient *rcc, PipeItem *base) -{ - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); - SpiceMarshaller *m = red_channel_client_get_marshaller(rcc); - - /* In semi-seamless migration (dest side), the connection is started from scratch, and - * we ignore any pipe item that arrives before the INIT msg is sent. - * For seamless we don't send INIT, and the connection continues from the same place - * it stopped on the src side. */ - if (!mcc->init_sent && !mcc->seamless_mig_dst && base->type != PIPE_ITEM_TYPE_MAIN_INIT) { - spice_printerr("Init msg for client %p was not sent yet " - "(client is probably during semi-seamless migration). Ignoring msg type %d", - rcc->client, base->type); - main_channel_release_pipe_item(rcc, base, FALSE); - return; - } - switch (base->type) { - case PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST: - main_channel_marshall_channels(rcc, m, base); - break; - case PIPE_ITEM_TYPE_MAIN_PING: - main_channel_marshall_ping(rcc, m, - SPICE_CONTAINEROF(base, PingPipeItem, base)); - break; - case PIPE_ITEM_TYPE_MAIN_MOUSE_MODE: - { - MouseModePipeItem *item = - SPICE_CONTAINEROF(base, MouseModePipeItem, base); - main_channel_marshall_mouse_mode(rcc, m, item); - break; - } - case PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED: - main_channel_marshall_agent_disconnected(rcc, m, base); - break; - case PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN: - main_channel_marshall_tokens(rcc, m, - SPICE_CONTAINEROF(base, TokensPipeItem, base)); - break; - case PIPE_ITEM_TYPE_MAIN_AGENT_DATA: - main_channel_marshall_agent_data(rcc, m, - SPICE_CONTAINEROF(base, AgentDataPipeItem, base)); - break; - case PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA: - main_channel_marshall_migrate_data_item(rcc, m, base); - break; - case PIPE_ITEM_TYPE_MAIN_INIT: - mcc->init_sent = TRUE; - main_channel_marshall_init(rcc, m, - SPICE_CONTAINEROF(base, InitPipeItem, base)); - break; - case PIPE_ITEM_TYPE_MAIN_NOTIFY: - main_channel_marshall_notify(rcc, m, - SPICE_CONTAINEROF(base, NotifyPipeItem, base)); - break; - case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN: - main_channel_marshall_migrate_begin(m, rcc, base); - break; - case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS: - main_channel_marshall_migrate_begin_seamless(m, rcc, base); - break; - case PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME: - main_channel_marshall_multi_media_time(rcc, m, - SPICE_CONTAINEROF(base, MultiMediaTimePipeItem, base)); - break; - case PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST: - main_channel_marshall_migrate_switch(m, rcc, base); - break; - case PIPE_ITEM_TYPE_MAIN_NAME: - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_NAME, base); - spice_marshall_msg_main_name(m, &SPICE_CONTAINEROF(base, NamePipeItem, base)->msg); - break; - case PIPE_ITEM_TYPE_MAIN_UUID: - red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_UUID, base); - spice_marshall_msg_main_uuid(m, &SPICE_CONTAINEROF(base, UuidPipeItem, base)->msg); - break; - case PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS: - main_channel_marshall_agent_connected(m, rcc, base); - break; - default: - break; - }; - red_channel_client_begin_send_message(rcc); -} - -static void main_channel_release_pipe_item(RedChannelClient *rcc, - PipeItem *base, int item_pushed) -{ - switch (base->type) { - case PIPE_ITEM_TYPE_MAIN_AGENT_DATA: { - AgentDataPipeItem *data = (AgentDataPipeItem *)base; - - data->free_data(data->data, data->opaque); - break; - } - case PIPE_ITEM_TYPE_MAIN_NOTIFY: { - NotifyPipeItem *data = (NotifyPipeItem *)base; - free(data->msg); - break; - } - default: - break; - } - free(base); -} - -static void main_channel_client_handle_migrate_connected(MainChannelClient *mcc, - int success, - int seamless) -{ - spice_printerr("client %p connected: %d seamless %d", mcc->base.client, success, seamless); - if (mcc->mig_wait_connect) { - MainChannel *main_channel = SPICE_CONTAINEROF(mcc->base.channel, MainChannel, base); - - mcc->mig_wait_connect = FALSE; - mcc->mig_connect_ok = success; - spice_assert(main_channel->num_clients_mig_wait); - spice_assert(!seamless || main_channel->num_clients_mig_wait == 1); - if (!--main_channel->num_clients_mig_wait) { - reds_on_main_migrate_connected(seamless && success); - } - } else { - if (success) { - spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client); - red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL); - } - } -} - -void main_channel_client_handle_migrate_dst_do_seamless(MainChannelClient *mcc, - uint32_t src_version) -{ - if (reds_on_migrate_dst_set_seamless(mcc, src_version)) { - mcc->seamless_mig_dst = TRUE; - red_channel_client_pipe_add_empty_msg(&mcc->base, - SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK); - } else { - red_channel_client_pipe_add_empty_msg(&mcc->base, - SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK); - } -} - -void main_channel_client_handle_migrate_end(MainChannelClient *mcc) -{ - if (!red_client_during_migrate_at_target(mcc->base.client)) { - spice_printerr("unexpected SPICE_MSGC_MIGRATE_END"); - return; - } - if (!red_channel_client_test_remote_cap(&mcc->base, - SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) { - spice_printerr("unexpected SPICE_MSGC_MIGRATE_END, " - "client does not support semi-seamless migration"); - return; - } - red_client_semi_seamless_migrate_complete(mcc->base.client); -} - -void main_channel_migrate_dst_complete(MainChannelClient *mcc) -{ - if (mcc->mig_wait_prev_complete) { - if (mcc->mig_wait_prev_try_seamless) { - spice_assert(mcc->base.channel->clients_num == 1); - red_channel_client_pipe_add_type(&mcc->base, - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS); - } else { - red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN); - } - mcc->mig_wait_connect = TRUE; - mcc->mig_wait_prev_complete = FALSE; - } -} - -static int main_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type, - void *message) -{ - MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); - - switch (type) { - case SPICE_MSGC_MAIN_AGENT_START: { - SpiceMsgcMainAgentStart *tokens; - - spice_printerr("agent start"); - if (!main_chan) { - return FALSE; - } - tokens = (SpiceMsgcMainAgentStart *)message; - reds_on_main_agent_start(mcc, tokens->num_tokens); - break; - } - case SPICE_MSGC_MAIN_AGENT_DATA: { - reds_on_main_agent_data(mcc, message, size); - break; - } - case SPICE_MSGC_MAIN_AGENT_TOKEN: { - SpiceMsgcMainAgentTokens *tokens; - - tokens = (SpiceMsgcMainAgentTokens *)message; - reds_on_main_agent_tokens(mcc, tokens->num_tokens); - break; - } - case SPICE_MSGC_MAIN_ATTACH_CHANNELS: - main_channel_push_channels(mcc); - break; - case SPICE_MSGC_MAIN_MIGRATE_CONNECTED: - main_channel_client_handle_migrate_connected(mcc, - TRUE /* success */, - FALSE /* seamless */); - break; - case SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS: - main_channel_client_handle_migrate_connected(mcc, - TRUE /* success */, - TRUE /* seamless */); - break; - case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR: - main_channel_client_handle_migrate_connected(mcc, FALSE, FALSE); - break; - case SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS: - main_channel_client_handle_migrate_dst_do_seamless(mcc, - ((SpiceMsgcMainMigrateDstDoSeamless *)message)->src_version); - break; - case SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST: - reds_on_main_mouse_mode_request(message, size); - break; - case SPICE_MSGC_PONG: { - SpiceMsgPing *ping = (SpiceMsgPing *)message; - uint64_t roundtrip; - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - roundtrip = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL - ping->timestamp; - - if (ping->id == mcc->net_test_id) { - switch (mcc->net_test_stage) { - case NET_TEST_STAGE_WARMUP: - mcc->net_test_id++; - mcc->net_test_stage = NET_TEST_STAGE_LATENCY; - mcc->latency = roundtrip; - break; - case NET_TEST_STAGE_LATENCY: - mcc->net_test_id++; - mcc->net_test_stage = NET_TEST_STAGE_RATE; - mcc->latency = MIN(mcc->latency, roundtrip); - break; - case NET_TEST_STAGE_RATE: - mcc->net_test_id = 0; - if (roundtrip <= mcc->latency) { - // probably high load on client or server result with incorrect values - spice_printerr("net test: invalid values, latency %" PRIu64 - " roundtrip %" PRIu64 ". assuming high" - " bandwidth", mcc->latency, roundtrip); - mcc->latency = 0; - mcc->net_test_stage = NET_TEST_STAGE_INVALID; - red_channel_client_start_connectivity_monitoring(&mcc->base, - CLIENT_CONNECTIVITY_TIMEOUT); - break; - } - mcc->bitrate_per_sec = (uint64_t)(NET_TEST_BYTES * 8) * 1000000 - / (roundtrip - mcc->latency); - mcc->net_test_stage = NET_TEST_STAGE_COMPLETE; - spice_printerr("net test: latency %f ms, bitrate %"PRIu64" bps (%f Mbps)%s", - (double)mcc->latency / 1000, - mcc->bitrate_per_sec, - (double)mcc->bitrate_per_sec / 1024 / 1024, - main_channel_client_is_low_bandwidth(mcc) ? " LOW BANDWIDTH" : ""); - red_channel_client_start_connectivity_monitoring(&mcc->base, - CLIENT_CONNECTIVITY_TIMEOUT); - break; - default: - spice_printerr("invalid net test stage, ping id %d test id %d stage %d", - ping->id, - mcc->net_test_id, - mcc->net_test_stage); - mcc->net_test_stage = NET_TEST_STAGE_INVALID; - } - break; - } else { - /* - * channel client monitors the connectivity using ping-pong messages - */ - red_channel_client_handle_message(rcc, size, type, message); - } -#ifdef RED_STATISTICS - reds_update_stat_value(roundtrip); -#endif - break; - } - case SPICE_MSGC_DISCONNECTING: - break; - case SPICE_MSGC_MAIN_MIGRATE_END: - main_channel_client_handle_migrate_end(mcc); - break; - default: - return red_channel_client_handle_message(rcc, size, type, message); - } - return TRUE; -} - -static uint8_t *main_channel_alloc_msg_rcv_buf(RedChannelClient *rcc, - uint16_t type, - uint32_t size) -{ - MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); - MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); - - if (type == SPICE_MSGC_MAIN_AGENT_DATA) { - return reds_get_agent_data_buffer(mcc, size); - } else { - return main_chan->recv_buf; - } -} - -static void main_channel_release_msg_rcv_buf(RedChannelClient *rcc, - uint16_t type, - uint32_t size, - uint8_t *msg) -{ - if (type == SPICE_MSGC_MAIN_AGENT_DATA) { - reds_release_agent_data_buffer(msg); - } -} - -static int main_channel_config_socket(RedChannelClient *rcc) -{ - return TRUE; -} - -static void main_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item) -{ -} - -static int main_channel_handle_migrate_flush_mark(RedChannelClient *rcc) -{ - spice_debug(NULL); - main_channel_push_migrate_data_item(SPICE_CONTAINEROF(rcc->channel, - MainChannel, base)); - return TRUE; -} - -#ifdef RED_STATISTICS -static void do_ping_client(MainChannelClient *mcc, - const char *opt, int has_interval, int interval) -{ - spice_printerr(""); - if (!opt) { - main_channel_client_push_ping(mcc, 0); - } else if (!strcmp(opt, "on")) { - if (has_interval && interval > 0) { - mcc->ping_interval = interval * 1000; - } - core->timer_start(mcc->ping_timer, mcc->ping_interval); - } else if (!strcmp(opt, "off")) { - core->timer_cancel(mcc->ping_timer); - } else { - return; - } -} - -static void ping_timer_cb(void *opaque) -{ - MainChannelClient *mcc = opaque; - - if (!red_channel_client_is_connected(&mcc->base)) { - spice_printerr("not connected to peer, ping off"); - core->timer_cancel(mcc->ping_timer); - return; - } - do_ping_client(mcc, NULL, 0, 0); - core->timer_start(mcc->ping_timer, mcc->ping_interval); -} -#endif /* RED_STATISTICS */ - -static MainChannelClient *main_channel_client_create(MainChannel *main_chan, RedClient *client, - RedsStream *stream, uint32_t connection_id, - int num_common_caps, uint32_t *common_caps, - int num_caps, uint32_t *caps) -{ - MainChannelClient *mcc = (MainChannelClient*) - red_channel_client_create(sizeof(MainChannelClient), &main_chan->base, - client, stream, FALSE, num_common_caps, - common_caps, num_caps, caps); - spice_assert(mcc != NULL); - mcc->connection_id = connection_id; - mcc->bitrate_per_sec = ~0; -#ifdef RED_STATISTICS - if (!(mcc->ping_timer = core->timer_add(ping_timer_cb, NULL))) { - spice_error("ping timer create failed"); - } - mcc->ping_interval = PING_INTERVAL; -#endif - return mcc; -} - -MainChannelClient *main_channel_link(MainChannel *channel, RedClient *client, - RedsStream *stream, uint32_t connection_id, int migration, - int num_common_caps, uint32_t *common_caps, int num_caps, - uint32_t *caps) -{ - MainChannelClient *mcc; - - spice_assert(channel); - - // TODO - migration - I removed it from channel creation, now put it - // into usage somewhere (not an issue until we return migration to it's - // former glory) - spice_printerr("add main channel client"); - mcc = main_channel_client_create(channel, client, stream, connection_id, - num_common_caps, common_caps, - num_caps, caps); - return mcc; -} - -int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen) -{ - return main_chan ? getsockname(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1; -} - -int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen) -{ - return main_chan ? getpeername(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1; -} - -// TODO: ? shouldn't it disonnect all clients? or shutdown all main_channels? -void main_channel_close(MainChannel *main_chan) -{ - int socketfd; - - if (main_chan && (socketfd = red_channel_get_first_socket(&main_chan->base)) != -1) { - close(socketfd); - } -} - -int main_channel_client_is_network_info_initialized(MainChannelClient *mcc) -{ - return mcc->net_test_stage == NET_TEST_STAGE_COMPLETE; -} - -int main_channel_client_is_low_bandwidth(MainChannelClient *mcc) -{ - // TODO: configurable? - return mcc->bitrate_per_sec < 10 * 1024 * 1024; -} - -uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc) -{ - return mcc->bitrate_per_sec; -} - -uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc) -{ - return mcc->latency / 1000; -} - -static void main_channel_client_migrate(RedChannelClient *rcc) -{ - reds_on_main_channel_migrate(SPICE_CONTAINEROF(rcc, MainChannelClient, base)); - red_channel_client_default_migrate(rcc); -} - -MainChannel* main_channel_init(void) -{ - RedChannel *channel; - ChannelCbs channel_cbs = { NULL, }; - ClientCbs client_cbs = {NULL, }; - - channel_cbs.config_socket = main_channel_config_socket; - channel_cbs.on_disconnect = main_channel_client_on_disconnect; - channel_cbs.send_item = main_channel_send_item; - channel_cbs.hold_item = main_channel_hold_pipe_item; - channel_cbs.release_item = main_channel_release_pipe_item; - channel_cbs.alloc_recv_buf = main_channel_alloc_msg_rcv_buf; - channel_cbs.release_recv_buf = main_channel_release_msg_rcv_buf; - channel_cbs.handle_migrate_flush_mark = main_channel_handle_migrate_flush_mark; - channel_cbs.handle_migrate_data = main_channel_handle_migrate_data; - - // TODO: set the migration flag of the channel - channel = red_channel_create_parser(sizeof(MainChannel), core, - SPICE_CHANNEL_MAIN, 0, - FALSE, /* handle_acks */ - spice_get_client_channel_parser(SPICE_CHANNEL_MAIN, NULL), - main_channel_handle_parsed, - &channel_cbs, - SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER); - spice_assert(channel); - red_channel_set_cap(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE); - red_channel_set_cap(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE); - - client_cbs.migrate = main_channel_client_migrate; - red_channel_register_client_cbs(channel, &client_cbs); - - return (MainChannel *)channel; -} - -RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc) -{ - spice_assert(mcc); - return &mcc->base; -} - -static int main_channel_connect_semi_seamless(MainChannel *main_channel) -{ - RingItem *client_link; - - RING_FOREACH(client_link, &main_channel->base.clients) { - MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, - base.channel_link); - if (red_channel_client_test_remote_cap(&mcc->base, - SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) { - if (red_client_during_migrate_at_target(mcc->base.client)) { - spice_printerr("client %p: wait till previous migration completes", mcc->base.client); - mcc->mig_wait_prev_complete = TRUE; - mcc->mig_wait_prev_try_seamless = FALSE; - } else { - red_channel_client_pipe_add_type(&mcc->base, - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN); - mcc->mig_wait_connect = TRUE; - } - mcc->mig_connect_ok = FALSE; - main_channel->num_clients_mig_wait++; - } - } - return main_channel->num_clients_mig_wait; -} - -static int main_channel_connect_seamless(MainChannel *main_channel) -{ - RingItem *client_link; - - spice_assert(main_channel->base.clients_num == 1); - - RING_FOREACH(client_link, &main_channel->base.clients) { - MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, - base.channel_link); - spice_assert(red_channel_client_test_remote_cap(&mcc->base, - SPICE_MAIN_CAP_SEAMLESS_MIGRATE)); - if (red_client_during_migrate_at_target(mcc->base.client)) { - spice_printerr("client %p: wait till previous migration completes", mcc->base.client); - mcc->mig_wait_prev_complete = TRUE; - mcc->mig_wait_prev_try_seamless = TRUE; - } else { - red_channel_client_pipe_add_type(&mcc->base, - PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS); - mcc->mig_wait_connect = TRUE; - } - mcc->mig_connect_ok = FALSE; - main_channel->num_clients_mig_wait++; - } - return main_channel->num_clients_mig_wait; -} - -int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target, - int try_seamless) -{ - main_channel_fill_mig_target(main_channel, mig_target); - main_channel->num_clients_mig_wait = 0; - - if (!main_channel_is_connected(main_channel)) { - return 0; - } - - if (!try_seamless) { - return main_channel_connect_semi_seamless(main_channel); - } else { - RingItem *client_item; - MainChannelClient *mcc; - - client_item = ring_get_head(&main_channel->base.clients); - mcc = SPICE_CONTAINEROF(client_item, MainChannelClient, base.channel_link); - - if (!red_channel_client_test_remote_cap(&mcc->base, - SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) { - return main_channel_connect_semi_seamless(main_channel); - } else { - return main_channel_connect_seamless(main_channel); - } - } - -} - -void main_channel_migrate_cancel_wait(MainChannel *main_chan) -{ - RingItem *client_link; - - RING_FOREACH(client_link, &main_chan->base.clients) { - MainChannelClient *mcc; - - mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link); - if (mcc->mig_wait_connect) { - spice_printerr("client %p cancel wait connect", mcc->base.client); - mcc->mig_wait_connect = FALSE; - mcc->mig_connect_ok = FALSE; - } - mcc->mig_wait_prev_complete = FALSE; - } - main_chan->num_clients_mig_wait = 0; -} - -int main_channel_migrate_src_complete(MainChannel *main_chan, int success) -{ - RingItem *client_link; - int semi_seamless_count = 0; - - spice_printerr(""); - - if (ring_is_empty(&main_chan->base.clients)) { - spice_printerr("no peer connected"); - return 0; - } - - RING_FOREACH(client_link, &main_chan->base.clients) { - MainChannelClient *mcc; - int semi_seamless_support; - - mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link); - semi_seamless_support = red_channel_client_test_remote_cap(&mcc->base, - SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE); - if (semi_seamless_support && mcc->mig_connect_ok) { - if (success) { - spice_printerr("client %p MIGRATE_END", mcc->base.client); - red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_END); - semi_seamless_count++; - } else { - spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client); - red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL); - } - } else { - if (success) { - spice_printerr("client %p SWITCH_HOST", mcc->base.client); - red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST); - } - } - mcc->mig_connect_ok = FALSE; - mcc->mig_wait_connect = FALSE; - } - return semi_seamless_count; -} diff --git a/server/main_channel.h b/server/main_channel.h deleted file mode 100644 index 9bd20f1..0000000 --- a/server/main_channel.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef __MAIN_CHANNEL_H__ -#define __MAIN_CHANNEL_H__ - -#include <stdint.h> -#include <spice/vd_agent.h> -#include "common/marshaller.h" -#include "red_channel.h" - -// TODO: Defines used to calculate receive buffer size, and also by reds.c -// other options: is to make a reds_main_consts.h, to duplicate defines. -#define REDS_AGENT_WINDOW_SIZE 10 -#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1 - -// approximate max receive message size for main channel -#define MAIN_CHANNEL_RECEIVE_BUF_SIZE \ - (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * SPICE_AGENT_MAX_DATA_SIZE) - -struct RedsMigSpice { - char *host; - char *cert_subject; - int port; - int sport; -}; -typedef struct RedsMigSpice RedsMigSpice; - -typedef struct MainChannel { - RedChannel base; - uint8_t recv_buf[MAIN_CHANNEL_RECEIVE_BUF_SIZE]; - RedsMigSpice mig_target; // TODO: add refs and release (afrer all clients completed migration in one way or the other?) - int num_clients_mig_wait; -} MainChannel; - - -MainChannel *main_channel_init(void); -RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t link_id); -/* This is a 'clone' from the reds.h Channel.link callback to allow passing link_id */ -MainChannelClient *main_channel_link(MainChannel *, RedClient *client, - RedsStream *stream, uint32_t link_id, int migration, int num_common_caps, - uint32_t *common_caps, int num_caps, uint32_t *caps); -void main_channel_close(MainChannel *main_chan); // not destroy, just socket close -void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode, int is_client_mouse_allowed); -void main_channel_push_agent_connected(MainChannel *main_chan); -void main_channel_push_agent_disconnected(MainChannel *main_chan); -void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens); -void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len, - spice_marshaller_item_free_func free_data, void *opaque); -void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate); -// TODO: huge. Consider making a reds_* interface for these functions -// and calling from main. -void main_channel_push_init(MainChannelClient *mcc, int display_channels_hint, - int current_mouse_mode, int is_client_mouse_allowed, int multi_media_time, - int ram_hint); -void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg); -void main_channel_push_multi_media_time(MainChannel *main_chan, int time); -int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen); -int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen); - -/* - * return TRUE if network test had been completed successfully. - * If FALSE, bitrate_per_sec is set to MAX_UINT64 and the roundtrip is set to 0 - */ -int main_channel_client_is_network_info_initialized(MainChannelClient *mcc); -int main_channel_client_is_low_bandwidth(MainChannelClient *mcc); -uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc); -uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc); - -int main_channel_is_connected(MainChannel *main_chan); -RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc); - -/* switch host migration */ -void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target); - -/* semi seamless migration */ - -/* returns the number of clients that we are waiting for their connection. - * try_seamless = 'true' when the seamless-migration=on in qemu command line */ -int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target, - int try_seamless); -void main_channel_migrate_cancel_wait(MainChannel *main_chan); -/* returns the number of clients for which SPICE_MSG_MAIN_MIGRATE_END was sent*/ -int main_channel_migrate_src_complete(MainChannel *main_chan, int success); -void main_channel_migrate_dst_complete(MainChannelClient *mcc); -void main_channel_push_name(MainChannelClient *mcc, const char *name); -void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16]); - -#endif diff --git a/server/main_dispatcher.c b/server/main_dispatcher.c deleted file mode 100644 index 6ad9d89..0000000 --- a/server/main_dispatcher.c +++ /dev/null @@ -1,217 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009-2015 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#include <config.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <pthread.h> - -#include "red_common.h" -#include "dispatcher.h" -#include "main_dispatcher.h" -#include "red_channel.h" -#include "reds.h" - -/* - * Main Dispatcher - * =============== - * - * Communication channel between any non main thread and the main thread. - * - * The main thread is that from which spice_server_init is called. - * - * Messages are single sized, sent from the non-main thread to the main-thread. - * No acknowledge is sent back. This prevents a possible deadlock with the main - * thread already waiting on a response for the existing red_dispatcher used - * by the worker thread. - * - * All events have three functions: - * main_dispatcher_<event_name> - non static, public function - * main_dispatcher_self_<event_name> - handler for main thread - * main_dispatcher_handle_<event_name> - handler for callback from main thread - * seperate from self because it may send an ack or do other work in the future. - */ - -typedef struct { - Dispatcher base; - SpiceCoreInterface *core; -} MainDispatcher; - -MainDispatcher main_dispatcher; - -enum { - MAIN_DISPATCHER_CHANNEL_EVENT = 0, - MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE, - MAIN_DISPATCHER_SET_MM_TIME_LATENCY, - MAIN_DISPATCHER_CLIENT_DISCONNECT, - - MAIN_DISPATCHER_NUM_MESSAGES -}; - -typedef struct MainDispatcherChannelEventMessage { - int event; - SpiceChannelEventInfo *info; -} MainDispatcherChannelEventMessage; - -typedef struct MainDispatcherMigrateSeamlessDstCompleteMessage { - RedClient *client; -} MainDispatcherMigrateSeamlessDstCompleteMessage; - -typedef struct MainDispatcherMmTimeLatencyMessage { - RedClient *client; - uint32_t latency; -} MainDispatcherMmTimeLatencyMessage; - -typedef struct MainDispatcherClientDisconnectMessage { - RedClient *client; -} MainDispatcherClientDisconnectMessage; - -/* channel_event - calls core->channel_event, must be done in main thread */ -static void main_dispatcher_self_handle_channel_event( - int event, - SpiceChannelEventInfo *info) -{ - reds_handle_channel_event(event, info); -} - -static void main_dispatcher_handle_channel_event(void *opaque, - void *payload) -{ - MainDispatcherChannelEventMessage *channel_event = payload; - - main_dispatcher_self_handle_channel_event(channel_event->event, - channel_event->info); -} - -void main_dispatcher_channel_event(int event, SpiceChannelEventInfo *info) -{ - MainDispatcherChannelEventMessage msg = {0,}; - - if (pthread_self() == main_dispatcher.base.self) { - main_dispatcher_self_handle_channel_event(event, info); - return; - } - msg.event = event; - msg.info = info; - dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_CHANNEL_EVENT, - &msg); -} - - -static void main_dispatcher_handle_migrate_complete(void *opaque, - void *payload) -{ - MainDispatcherMigrateSeamlessDstCompleteMessage *mig_complete = payload; - - reds_on_client_seamless_migrate_complete(mig_complete->client); - red_client_unref(mig_complete->client); -} - -static void main_dispatcher_handle_mm_time_latency(void *opaque, - void *payload) -{ - MainDispatcherMmTimeLatencyMessage *msg = payload; - reds_set_client_mm_time_latency(msg->client, msg->latency); - red_client_unref(msg->client); -} - -static void main_dispatcher_handle_client_disconnect(void *opaque, - void *payload) -{ - MainDispatcherClientDisconnectMessage *msg = payload; - - spice_debug("client=%p", msg->client); - reds_client_disconnect(msg->client); - red_client_unref(msg->client); -} - -void main_dispatcher_seamless_migrate_dst_complete(RedClient *client) -{ - MainDispatcherMigrateSeamlessDstCompleteMessage msg; - - if (pthread_self() == main_dispatcher.base.self) { - reds_on_client_seamless_migrate_complete(client); - return; - } - - msg.client = red_client_ref(client); - dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE, - &msg); -} - -void main_dispatcher_set_mm_time_latency(RedClient *client, uint32_t latency) -{ - MainDispatcherMmTimeLatencyMessage msg; - - if (pthread_self() == main_dispatcher.base.self) { - reds_set_client_mm_time_latency(client, latency); - return; - } - - msg.client = red_client_ref(client); - msg.latency = latency; - dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_SET_MM_TIME_LATENCY, - &msg); -} - -void main_dispatcher_client_disconnect(RedClient *client) -{ - MainDispatcherClientDisconnectMessage msg; - - if (!client->disconnecting) { - spice_debug("client %p", client); - msg.client = red_client_ref(client); - dispatcher_send_message(&main_dispatcher.base, MAIN_DISPATCHER_CLIENT_DISCONNECT, - &msg); - } else { - spice_debug("client %p already during disconnection", client); - } -} - -static void dispatcher_handle_read(int fd, int event, void *opaque) -{ - Dispatcher *dispatcher = opaque; - - dispatcher_handle_recv_read(dispatcher); -} - -/* - * FIXME: - * Reds routines shouldn't be exposed. Instead reds.c should register the callbacks, - * and the corresponding operations should be made only via main_dispatcher. - */ -void main_dispatcher_init(SpiceCoreInterface *core) -{ - memset(&main_dispatcher, 0, sizeof(main_dispatcher)); - main_dispatcher.core = core; - dispatcher_init(&main_dispatcher.base, MAIN_DISPATCHER_NUM_MESSAGES, &main_dispatcher.base); - core->watch_add(main_dispatcher.base.recv_fd, SPICE_WATCH_EVENT_READ, - dispatcher_handle_read, &main_dispatcher.base); - dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_CHANNEL_EVENT, - main_dispatcher_handle_channel_event, - sizeof(MainDispatcherChannelEventMessage), 0 /* no ack */); - dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE, - main_dispatcher_handle_migrate_complete, - sizeof(MainDispatcherMigrateSeamlessDstCompleteMessage), 0 /* no ack */); - dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_SET_MM_TIME_LATENCY, - main_dispatcher_handle_mm_time_latency, - sizeof(MainDispatcherMmTimeLatencyMessage), 0 /* no ack */); - dispatcher_register_handler(&main_dispatcher.base, MAIN_DISPATCHER_CLIENT_DISCONNECT, - main_dispatcher_handle_client_disconnect, - sizeof(MainDispatcherClientDisconnectMessage), 0 /* no ack */); -} diff --git a/server/main_dispatcher.h b/server/main_dispatcher.h deleted file mode 100644 index af40093..0000000 --- a/server/main_dispatcher.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009-2015 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifndef MAIN_DISPATCHER_H -#define MAIN_DISPATCHER_H - -#include <spice.h> -#include "red_channel.h" - -void main_dispatcher_channel_event(int event, SpiceChannelEventInfo *info); -void main_dispatcher_seamless_migrate_dst_complete(RedClient *client); -void main_dispatcher_set_mm_time_latency(RedClient *client, uint32_t latency); -/* - * Disconnecting the client is always executed asynchronously, - * in order to protect from expired references in the routines - * that triggered the client destruction. - */ -void main_dispatcher_client_disconnect(RedClient *client); - -void main_dispatcher_init(SpiceCoreInterface *core); - -#endif //MAIN_DISPATCHER_H diff --git a/server/memslot.c b/server/memslot.c new file mode 100644 index 0000000..e7ee04c --- /dev/null +++ b/server/memslot.c @@ -0,0 +1,184 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009,2010 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <inttypes.h> + +#include "red_common.h" +#include "memslot.h" + +static unsigned long __get_clean_virt(RedMemSlotInfo *info, QXLPHYSICAL addr) +{ + return addr & info->memslot_clean_virt_mask; +} + +static void print_memslots(RedMemSlotInfo *info) +{ + int i; + int x; + + for (i = 0; i < info->num_memslots_groups; ++i) { + for (x = 0; x < info->num_memslots; ++x) { + if (!info->mem_slots[i][x].virt_start_addr && + !info->mem_slots[i][x].virt_end_addr) { + continue; + } + printf("id %d, group %d, virt start %lx, virt end %lx, generation %u, delta %lx\n", + x, i, info->mem_slots[i][x].virt_start_addr, + info->mem_slots[i][x].virt_end_addr, info->mem_slots[i][x].generation, + info->mem_slots[i][x].address_delta); + } + } +} + +/* return 1 if validation successfull, 0 otherwise */ +int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id, + uint32_t add_size, uint32_t group_id) +{ + MemSlot *slot; + + slot = &info->mem_slots[group_id][slot_id]; + if ((virt + add_size) < virt) { + spice_critical("virtual address overlap"); + return 0; + } + + if (virt < slot->virt_start_addr || (virt + add_size) > slot->virt_end_addr) { + print_memslots(info); + spice_critical("virtual address out of range\n" + " virt=0x%lx+0x%x slot_id=%d group_id=%d\n" + " slot=0x%lx-0x%lx delta=0x%lx", + virt, add_size, slot_id, group_id, + slot->virt_start_addr, slot->virt_end_addr, slot->address_delta); + return 0; + } + return 1; +} + +/* + * return virtual address if successful, which may be 0. + * returns 0 and sets error to 1 if an error condition occurs. + */ +unsigned long memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size, + int group_id, int *error) +{ + int slot_id; + int generation; + unsigned long h_virt; + + MemSlot *slot; + + *error = 0; + if (group_id > info->num_memslots_groups) { + spice_critical("group_id too big"); + *error = 1; + return 0; + } + + slot_id = memslot_get_id(info, addr); + if (slot_id > info->num_memslots) { + print_memslots(info); + spice_critical("slot_id %d too big, addr=%" PRIx64, slot_id, addr); + *error = 1; + return 0; + } + + slot = &info->mem_slots[group_id][slot_id]; + + generation = memslot_get_generation(info, addr); + if (generation != slot->generation) { + print_memslots(info); + spice_critical("address generation is not valid, group_id %d, slot_id %d, gen %d, slot_gen %d\n", + group_id, slot_id, generation, slot->generation); + *error = 1; + return 0; + } + + h_virt = __get_clean_virt(info, addr); + h_virt += slot->address_delta; + + if (!memslot_validate_virt(info, h_virt, slot_id, add_size, group_id)) { + *error = 1; + return 0; + } + + return h_virt; +} + +void memslot_info_init(RedMemSlotInfo *info, + uint32_t num_groups, uint32_t num_slots, + uint8_t generation_bits, + uint8_t id_bits, + uint8_t internal_groupslot_id) +{ + uint32_t i; + + spice_return_if_fail(num_slots > 0); + spice_return_if_fail(num_groups > 0); + + info->num_memslots_groups = num_groups; + info->num_memslots = num_slots; + info->generation_bits = generation_bits; + info->mem_slot_bits = id_bits; + info->internal_groupslot_id = internal_groupslot_id; + + info->mem_slots = spice_new(MemSlot *, num_groups); + + for (i = 0; i < num_groups; ++i) { + info->mem_slots[i] = spice_new0(MemSlot, num_slots); + } + + /* TODO: use QXLPHYSICAL_BITS */ + info->memslot_id_shift = 64 - info->mem_slot_bits; + info->memslot_gen_shift = 64 - (info->mem_slot_bits + info->generation_bits); + info->memslot_gen_mask = ~((QXLPHYSICAL)-1 << info->generation_bits); + info->memslot_clean_virt_mask = (((QXLPHYSICAL)(-1)) >> + (info->mem_slot_bits + info->generation_bits)); +} + +void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id, + uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end, + uint32_t generation) +{ + spice_return_if_fail(info->num_memslots_groups > slot_group_id); + spice_return_if_fail(info->num_memslots > slot_id); + + info->mem_slots[slot_group_id][slot_id].address_delta = addr_delta; + info->mem_slots[slot_group_id][slot_id].virt_start_addr = virt_start; + info->mem_slots[slot_group_id][slot_id].virt_end_addr = virt_end; + info->mem_slots[slot_group_id][slot_id].generation = generation; +} + +void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id) +{ + spice_return_if_fail(info->num_memslots_groups > slot_group_id); + spice_return_if_fail(info->num_memslots > slot_id); + + info->mem_slots[slot_group_id][slot_id].virt_start_addr = 0; + info->mem_slots[slot_group_id][slot_id].virt_end_addr = 0; +} + +void memslot_info_reset(RedMemSlotInfo *info) +{ + uint32_t i; + for (i = 0; i < info->num_memslots_groups; ++i) { + memset(info->mem_slots[i], 0, sizeof(MemSlot) * info->num_memslots); + } +} diff --git a/server/memslot.h b/server/memslot.h new file mode 100644 index 0000000..1fba4b8 --- /dev/null +++ b/server/memslot.h @@ -0,0 +1,72 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009,2010 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MEMSLOT_H_ +#define MEMSLOT_H_ + +#include "red_common.h" + +#include <spice/qxl_dev.h> + +typedef struct MemSlot { + int generation; + unsigned long virt_start_addr; + unsigned long virt_end_addr; + long address_delta; +} MemSlot; + +typedef struct RedMemSlotInfo { + MemSlot **mem_slots; + uint32_t num_memslots_groups; + uint32_t num_memslots; + uint8_t mem_slot_bits; + uint8_t generation_bits; + uint8_t memslot_id_shift; + uint8_t memslot_gen_shift; + uint8_t internal_groupslot_id; + unsigned long memslot_gen_mask; + unsigned long memslot_clean_virt_mask; +} RedMemSlotInfo; + +static inline int memslot_get_id(RedMemSlotInfo *info, uint64_t addr) +{ + return addr >> info->memslot_id_shift; +} + +static inline int memslot_get_generation(RedMemSlotInfo *info, uint64_t addr) +{ + return (addr >> info->memslot_gen_shift) & info->memslot_gen_mask; +} + +int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id, + uint32_t add_size, uint32_t group_id); +unsigned long memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size, + int group_id, int *error); + +void memslot_info_init(RedMemSlotInfo *info, + uint32_t num_groups, uint32_t num_slots, + uint8_t generation_bits, + uint8_t id_bits, + uint8_t internal_groupslot_id); +void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id, + uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end, + uint32_t generation); +void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id); +void memslot_info_reset(RedMemSlotInfo *info); + +#endif diff --git a/server/migration-protocol.h b/server/migration-protocol.h new file mode 100644 index 0000000..c1d97ef --- /dev/null +++ b/server/migration-protocol.h @@ -0,0 +1,213 @@ +/* + Copyright (C) 2012 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _H_MIGRATION_PROTOCOL +#define _H_MIGRATION_PROTOCOL + +#include <spice/macros.h> +#include <spice/vd_agent.h> +#include "glz-encoder-dict.h" + +/* ************************************************ + * src-server to dst-server migration data messages + * ************************************************/ + +/* increase the version when the version of any + * of the migration data messages is increased */ +#define SPICE_MIGRATION_PROTOCOL_VERSION 1 + +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataHeader { + uint32_t magic; + uint32_t version; +} SpiceMigrateDataHeader; + +/* ******************** + * Char device base + * *******************/ + +/* increase the version of descendent char devices when this + * version is increased */ +#define SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION 1 + +/* Should be the first field of any of the char_devices migration data (see write_data_ptr) */ +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataCharDevice { + uint32_t version; + uint8_t connected; + uint32_t num_client_tokens; + uint32_t num_send_tokens; + uint32_t write_size; /* write to dev */ + uint32_t write_num_client_tokens; /* how many messages from the client are part of the write_data */ + uint32_t write_data_ptr; /* offset from + SpiceMigrateDataCharDevice - sizeof(SpiceMigrateDataHeader) */ +} SpiceMigrateDataCharDevice; + +/* ******** + * spicevmc + * ********/ + +#define SPICE_MIGRATE_DATA_SPICEVMC_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION + is increased */ +#define SPICE_MIGRATE_DATA_SPICEVMC_MAGIC SPICE_MAGIC_CONST("SVMD") +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataSpiceVmc { + SpiceMigrateDataCharDevice base; +} SpiceMigrateDataSpiceVmc; + +/* ********* + * smartcard + * *********/ + +#define SPICE_MIGRATE_DATA_SMARTCARD_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION + is increased */ +#define SPICE_MIGRATE_DATA_SMARTCARD_MAGIC SPICE_MAGIC_CONST("SCMD") +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataSmartcard { + SpiceMigrateDataCharDevice base; + uint8_t reader_added; + uint32_t read_size; /* partial data read from dev */ + uint32_t read_data_ptr; +} SpiceMigrateDataSmartcard; + +/* ********************************* + * main channel (mainly guest agent) + * *********************************/ +#define SPICE_MIGRATE_DATA_MAIN_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION + is increased */ +#define SPICE_MIGRATE_DATA_MAIN_MAGIC SPICE_MAGIC_CONST("MNMD") + +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataMain { + SpiceMigrateDataCharDevice agent_base; + uint8_t client_agent_started; /* for discarding messages */ + + struct __attribute__ ((__packed__)) { + /* partial data read from device. Such data is stored only + * if the chunk header or the entire msg header haven't yet been read completely. + * Once the headers are read, partial reads of chunks can be sent as + * smaller chunks to the client, without the roundtrip overhead of migration data */ + uint32_t chunk_header_size; + VDIChunkHeader chunk_header; + uint8_t msg_header_done; + uint32_t msg_header_partial_len; + uint32_t msg_header_ptr; + uint32_t msg_remaining; + uint8_t msg_filter_result; + } agent2client; + + struct __attribute__ ((__packed__)) { + uint32_t msg_remaining; + uint8_t msg_filter_result; + } client2agent; +} SpiceMigrateDataMain; + +/* **************** + * display channel + * ***************/ + +#define SPICE_MIGRATE_DATA_DISPLAY_VERSION 1 +#define SPICE_MIGRATE_DATA_DISPLAY_MAGIC SPICE_MAGIC_CONST("DCMD") + +/* + * TODO: store the cache and dictionary data only in one channel (the + * freezer). + * TODO: optimizations: don't send surfaces information if it will be faster + * to resend the surfaces on-demand. + * */ +#define MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS 4 + +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataDisplay { + uint64_t message_serial; + uint8_t low_bandwidth_setting; + + /* + * Synchronizing the shared pixmap cache. + * For now, the cache is not migrated, and instead, we reset it and send + * SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS to the client. + * In order to keep the client and server caches consistent: + * The channel which freezed the cache on the src side, unfreezes it + * on the dest side, and increases its generation (see 'reset' in red_client_shared_cach.h). + * In order to enforce that images that are added to the cache by other channels + * will reach the client only after SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS, + * we send SPICE_MSG_WAIT_FOR_CHANNELS + * (see the generation mismatch handling in 'add' in red_client_shared_cach.h). + */ + uint8_t pixmap_cache_id; + int64_t pixmap_cache_size; + uint8_t pixmap_cache_freezer; + uint64_t pixmap_cache_clients[MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS]; + + uint8_t glz_dict_id; + GlzEncDictRestoreData glz_dict_data; + + uint32_t surfaces_at_client_ptr; /* reference to MigrateDisplaySurfacesAtClientLossless/Lossy. + Lossy: when jpeg-wan-compression(qemu cmd line)=always + or when jpeg-wan-compression=auto, + and low_bandwidth_setting=TRUE */ + +} SpiceMigrateDataDisplay; + +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataRect { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +} SpiceMigrateDataRect; + +typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfaceLossless { + uint32_t id; +} MigrateDisplaySurfaceLossless; + +typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfaceLossy { + uint32_t id; + SpiceMigrateDataRect lossy_rect; +} MigrateDisplaySurfaceLossy; + +typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfacesAtClientLossless { + uint32_t num_surfaces; + MigrateDisplaySurfaceLossless surfaces[0]; +} MigrateDisplaySurfacesAtClientLossless; + +typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfacesAtClientLossy { + uint32_t num_surfaces; + MigrateDisplaySurfaceLossy surfaces[0]; +} MigrateDisplaySurfacesAtClientLossy; + +/* **************** + * inputs channel + * ***************/ + +#define SPICE_MIGRATE_DATA_INPUTS_VERSION 1 +#define SPICE_MIGRATE_DATA_INPUTS_MAGIC SPICE_MAGIC_CONST("ICMD") + + +typedef struct __attribute__ ((__packed__)) SpiceMigrateDataInputs { + uint16_t motion_count; +} SpiceMigrateDataInputs; + +static inline int migration_protocol_validate_header(SpiceMigrateDataHeader *header, + uint32_t magic, + uint32_t version) +{ + if (header->magic != magic) { + spice_error("bad magic %u (!= %u)", header->magic, magic); + return FALSE; + } + if (header->version > version) { + spice_error("unsupported version %u (> %u)", header->version, version); + return FALSE; + } + return TRUE; +} + +#endif diff --git a/server/migration_protocol.h b/server/migration_protocol.h deleted file mode 100644 index 21d3ec8..0000000 --- a/server/migration_protocol.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - Copyright (C) 2012 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef _H_MIGRATION_PROTOCOL -#define _H_MIGRATION_PROTOCOL - -#include <spice/macros.h> -#include <spice/vd_agent.h> -#include "glz_encoder_dictionary.h" - -/* ************************************************ - * src-server to dst-server migration data messages - * ************************************************/ - -/* increase the version when the version of any - * of the migration data messages is increased */ -#define SPICE_MIGRATION_PROTOCOL_VERSION 1 - -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataHeader { - uint32_t magic; - uint32_t version; -} SpiceMigrateDataHeader; - -/* ******************** - * Char device base - * *******************/ - -/* increase the version of descendent char devices when this - * version is increased */ -#define SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION 1 - -/* Should be the first field of any of the char_devices migration data (see write_data_ptr) */ -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataCharDevice { - uint32_t version; - uint8_t connected; - uint32_t num_client_tokens; - uint32_t num_send_tokens; - uint32_t write_size; /* write to dev */ - uint32_t write_num_client_tokens; /* how many messages from the client are part of the write_data */ - uint32_t write_data_ptr; /* offset from - SpiceMigrateDataCharDevice - sizeof(SpiceMigrateDataHeader) */ -} SpiceMigrateDataCharDevice; - -/* ******** - * spicevmc - * ********/ - -#define SPICE_MIGRATE_DATA_SPICEVMC_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION - is increased */ -#define SPICE_MIGRATE_DATA_SPICEVMC_MAGIC SPICE_MAGIC_CONST("SVMD") -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataSpiceVmc { - SpiceMigrateDataCharDevice base; -} SpiceMigrateDataSpiceVmc; - -/* ********* - * smartcard - * *********/ - -#define SPICE_MIGRATE_DATA_SMARTCARD_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION - is increased */ -#define SPICE_MIGRATE_DATA_SMARTCARD_MAGIC SPICE_MAGIC_CONST("SCMD") -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataSmartcard { - SpiceMigrateDataCharDevice base; - uint8_t reader_added; - uint32_t read_size; /* partial data read from dev */ - uint32_t read_data_ptr; -} SpiceMigrateDataSmartcard; - -/* ********************************* - * main channel (mainly guest agent) - * *********************************/ -#define SPICE_MIGRATE_DATA_MAIN_VERSION 1 /* NOTE: increase version when CHAR_DEVICE_VERSION - is increased */ -#define SPICE_MIGRATE_DATA_MAIN_MAGIC SPICE_MAGIC_CONST("MNMD") - -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataMain { - SpiceMigrateDataCharDevice agent_base; - uint8_t client_agent_started; /* for discarding messages */ - - struct __attribute__ ((__packed__)) { - /* partial data read from device. Such data is stored only - * if the chunk header or the entire msg header haven't yet been read completely. - * Once the headers are read, partial reads of chunks can be sent as - * smaller chunks to the client, without the roundtrip overhead of migration data */ - uint32_t chunk_header_size; - VDIChunkHeader chunk_header; - uint8_t msg_header_done; - uint32_t msg_header_partial_len; - uint32_t msg_header_ptr; - uint32_t msg_remaining; - uint8_t msg_filter_result; - } agent2client; - - struct __attribute__ ((__packed__)) { - uint32_t msg_remaining; - uint8_t msg_filter_result; - } client2agent; -} SpiceMigrateDataMain; - -/* **************** - * display channel - * ***************/ - -#define SPICE_MIGRATE_DATA_DISPLAY_VERSION 1 -#define SPICE_MIGRATE_DATA_DISPLAY_MAGIC SPICE_MAGIC_CONST("DCMD") - -/* - * TODO: store the cache and dictionary data only in one channel (the - * freezer). - * TODO: optimizations: don't send surfaces information if it will be faster - * to resend the surfaces on-demand. - * */ -#define MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS 4 - -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataDisplay { - uint64_t message_serial; - uint8_t low_bandwidth_setting; - - /* - * Synchronizing the shared pixmap cache. - * For now, the cache is not migrated, and instead, we reset it and send - * SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS to the client. - * In order to keep the client and server caches consistent: - * The channel which freezed the cache on the src side, unfreezes it - * on the dest side, and increases its generation (see 'reset' in red_client_shared_cach.h). - * In order to enforce that images that are added to the cache by other channels - * will reach the client only after SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS, - * we send SPICE_MSG_WAIT_FOR_CHANNELS - * (see the generation mismatch handling in 'add' in red_client_shared_cach.h). - */ - uint8_t pixmap_cache_id; - int64_t pixmap_cache_size; - uint8_t pixmap_cache_freezer; - uint64_t pixmap_cache_clients[MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS]; - - uint8_t glz_dict_id; - GlzEncDictRestoreData glz_dict_data; - - uint32_t surfaces_at_client_ptr; /* reference to MigrateDisplaySurfacesAtClientLossless/Lossy. - Lossy: when jpeg-wan-compression(qemu cmd line)=always - or when jpeg-wan-compression=auto, - and low_bandwidth_setting=TRUE */ - -} SpiceMigrateDataDisplay; - -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataRect { - int32_t left; - int32_t top; - int32_t right; - int32_t bottom; -} SpiceMigrateDataRect; - -typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfaceLossless { - uint32_t id; -} MigrateDisplaySurfaceLossless; - -typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfaceLossy { - uint32_t id; - SpiceMigrateDataRect lossy_rect; -} MigrateDisplaySurfaceLossy; - -typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfacesAtClientLossless { - uint32_t num_surfaces; - MigrateDisplaySurfaceLossless surfaces[0]; -} MigrateDisplaySurfacesAtClientLossless; - -typedef struct __attribute__ ((__packed__)) MigrateDisplaySurfacesAtClientLossy { - uint32_t num_surfaces; - MigrateDisplaySurfaceLossy surfaces[0]; -} MigrateDisplaySurfacesAtClientLossy; - -/* **************** - * inputs channel - * ***************/ - -#define SPICE_MIGRATE_DATA_INPUTS_VERSION 1 -#define SPICE_MIGRATE_DATA_INPUTS_MAGIC SPICE_MAGIC_CONST("ICMD") - - -typedef struct __attribute__ ((__packed__)) SpiceMigrateDataInputs { - uint16_t motion_count; -} SpiceMigrateDataInputs; - -static inline int migration_protocol_validate_header(SpiceMigrateDataHeader *header, - uint32_t magic, - uint32_t version) -{ - if (header->magic != magic) { - spice_error("bad magic %u (!= %u)", header->magic, magic); - return FALSE; - } - if (header->version > version) { - spice_error("unsupported version %u (> %u)", header->version, version); - return FALSE; - } - return TRUE; -} - -#endif diff --git a/server/mjpeg-encoder.c b/server/mjpeg-encoder.c new file mode 100644 index 0000000..04c95a6 --- /dev/null +++ b/server/mjpeg-encoder.c @@ -0,0 +1,1375 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "red_common.h" +#include "mjpeg-encoder.h" +#include "utils.h" +#include <jerror.h> +#include <jpeglib.h> +#include <inttypes.h> + +#define MJPEG_MAX_FPS 25 +#define MJPEG_MIN_FPS 1 + +#define MJPEG_QUALITY_SAMPLE_NUM 7 +static const int mjpeg_quality_samples[MJPEG_QUALITY_SAMPLE_NUM] = {20, 30, 40, 50, 60, 70, 80}; + +#define MJPEG_LEGACY_STATIC_QUALITY_ID 5 // jpeg quality 70 + +#define MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH 10 +#define MJPEG_IMPROVE_QUALITY_FPS_PERMISSIVE_TH 5 + +#define MJPEG_AVERAGE_SIZE_WINDOW 3 + +#define MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES 3 +#define MJPEG_LOW_FPS_RATE_TH 3 + +#define MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL 1 +#define MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH 0.1 + +/* + * acting on positive client reports only if enough frame mm time + * has passed since the last bit rate change and the report. + * time + */ +#define MJPEG_CLIENT_POSITIVE_REPORT_TIMEOUT 2000 +#define MJPEG_CLIENT_POSITIVE_REPORT_STRICT_TIMEOUT 3000 + +#define MJPEG_ADJUST_FPS_TIMEOUT 500 + +/* + * avoid interrupting the playback when there are temporary + * incidents of instability (with respect to server and client drops) + */ +#define MJPEG_MAX_CLIENT_PLAYBACK_DELAY 5000 // 5 sec + +/* + * The stream starts after lossless frames were sent to the client, + * and without rate control (except for pipe congestion). Thus, on the beginning + * of the stream, we might observe frame drops on the client and server side which + * are not necessarily related to mis-estimation of the bit rate, and we would + * like to wait till the stream stabilizes. + */ +#define MJPEG_WARMUP_TIME 3000LL // 3 sec + +enum { + MJPEG_QUALITY_EVAL_TYPE_SET, + MJPEG_QUALITY_EVAL_TYPE_UPGRADE, + MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE, +}; + +enum { + MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE, + MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE, +}; + +typedef struct MJpegEncoderQualityEval { + int type; + int reason; + + uint64_t encoded_size_by_quality[MJPEG_QUALITY_SAMPLE_NUM]; + /* lower limit for the current evaluation round */ + int min_quality_id; + int min_quality_fps; // min fps for the given quality + /* upper limit for the current evaluation round */ + int max_quality_id; + int max_quality_fps; // max fps for the given quality + /* tracking the best sampled fps so far */ + int max_sampled_fps; + int max_sampled_fps_quality_id; +} MJpegEncoderQualityEval; + +typedef struct MJpegEncoderClientState { + int max_video_latency; + uint32_t max_audio_latency; +} MJpegEncoderClientState; + +typedef struct MJpegEncoderServerState { + uint32_t num_frames_encoded; + uint32_t num_frames_dropped; +} MJpegEncoderServerState; + +typedef struct MJpegEncoderBitRateInfo { + uint64_t change_start_time; + uint64_t last_frame_time; + uint32_t change_start_mm_time; + int was_upgraded; + + /* gathering data about the frames that + * were encoded since the last bit rate change*/ + uint32_t num_enc_frames; + uint64_t sum_enc_size; +} MJpegEncoderBitRateInfo; + +/* + * Adjusting the stream jpeg quality and frame rate (fps): + * When during_quality_eval=TRUE, we compress different frames with different + * jpeg quality. By considering (1) the resulting compression ratio, and (2) the available + * bit rate, we evaluate the max frame frequency for the stream with the given quality, + * and we choose the highest quality that will allow a reasonable frame rate. + * during_quality_eval is set for new streams and can also be set any time we want + * to re-evaluate the stream parameters (e.g., when the bit rate and/or + * compressed frame size significantly change). + */ +typedef struct MJpegEncoderRateControl { + int during_quality_eval; + MJpegEncoderQualityEval quality_eval_data; + MJpegEncoderBitRateInfo bit_rate_info; + MJpegEncoderClientState client_state; + MJpegEncoderServerState server_state; + + uint64_t byte_rate; + int quality_id; + uint32_t fps; + double adjusted_fps; + uint64_t adjusted_fps_start_time; + uint64_t adjusted_fps_num_frames; + + /* the encoded frame size which the quality and the fps evaluation was based upon */ + uint64_t base_enc_size; + + uint64_t last_enc_size; + + uint64_t sum_recent_enc_size; + uint32_t num_recent_enc_frames; + + uint64_t warmup_start_time; +} MJpegEncoderRateControl; + +struct MJpegEncoder { + uint8_t *row; + uint32_t row_size; + int first_frame; + + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + + unsigned int bytes_per_pixel; /* bytes per pixel of the input buffer */ + void (*pixel_converter)(void *src, uint8_t *dest); + + MJpegEncoderRateControl rate_control; + MJpegEncoderRateControlCbs cbs; + void *cbs_opaque; + + /* stats */ + uint64_t starting_bit_rate; + uint64_t avg_quality; + uint32_t num_frames; +}; + +static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder); +static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size, + uint64_t byte_rate, + uint32_t latency); + +static inline int rate_control_is_active(MJpegEncoder* encoder) +{ + return encoder->cbs.get_roundtrip_ms != NULL; +} + +void mjpeg_encoder_destroy(MJpegEncoder *encoder) +{ + free(encoder->cinfo.dest); + jpeg_destroy_compress(&encoder->cinfo); + free(encoder->row); + free(encoder); +} + +static uint8_t mjpeg_encoder_get_bytes_per_pixel(MJpegEncoder *encoder) +{ + return encoder->bytes_per_pixel; +} + +#ifndef JCS_EXTENSIONS +/* Pixel conversion routines */ +static void pixel_rgb24bpp_to_24(void *src_ptr, uint8_t *dest) +{ + uint8_t *src = src_ptr; + /* libjpegs stores rgb, spice/win32 stores bgr */ + *dest++ = src[2]; /* red */ + *dest++ = src[1]; /* green */ + *dest++ = src[0]; /* blue */ +} + +static void pixel_rgb32bpp_to_24(void *src, uint8_t *dest) +{ + uint32_t pixel = *(uint32_t *)src; + *dest++ = (pixel >> 16) & 0xff; + *dest++ = (pixel >> 8) & 0xff; + *dest++ = (pixel >> 0) & 0xff; +} +#endif + +static void pixel_rgb16bpp_to_24(void *src, uint8_t *dest) +{ + uint16_t pixel = *(uint16_t *)src; + *dest++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7); + *dest++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7); + *dest++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7); +} + + +/* code from libjpeg 8 to handle compression to a memory buffer + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * Modified 2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + */ +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + unsigned char ** outbuffer; /* target buffer */ + size_t * outsize; + uint8_t * buffer; /* start of buffer */ + size_t bufsize; +} mem_destination_mgr; + +static void init_mem_destination(j_compress_ptr cinfo) +{ +} + +static boolean empty_mem_output_buffer(j_compress_ptr cinfo) +{ + size_t nextsize; + uint8_t * nextbuffer; + mem_destination_mgr *dest = (mem_destination_mgr *) cinfo->dest; + + /* Try to allocate new buffer with double size */ + nextsize = dest->bufsize * 2; + nextbuffer = malloc(nextsize); + + if (nextbuffer == NULL) + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10); + + memcpy(nextbuffer, dest->buffer, dest->bufsize); + + free(dest->buffer); + + dest->pub.next_output_byte = nextbuffer + dest->bufsize; + dest->pub.free_in_buffer = dest->bufsize; + + dest->buffer = nextbuffer; + dest->bufsize = nextsize; + + return TRUE; +} + +static void term_mem_destination(j_compress_ptr cinfo) +{ + mem_destination_mgr *dest = (mem_destination_mgr *) cinfo->dest; + + *dest->outbuffer = dest->buffer; + *dest->outsize = dest->bufsize; +} + +/* + * Prepare for output to a memory buffer. + * The caller may supply an own initial buffer with appropriate size. + * Otherwise, or when the actual data output exceeds the given size, + * the library adapts the buffer size as necessary. + * The standard library functions malloc/free are used for allocating + * larger memory, so the buffer is available to the application after + * finishing compression, and then the application is responsible for + * freeing the requested memory. + */ + +static void +spice_jpeg_mem_dest(j_compress_ptr cinfo, + unsigned char ** outbuffer, size_t * outsize) +{ + mem_destination_mgr *dest; +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ + + if (outbuffer == NULL || outsize == NULL) /* sanity check */ + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same buffer without re-executing jpeg_mem_dest. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = spice_malloc(sizeof(mem_destination_mgr)); + } + + dest = (mem_destination_mgr *) cinfo->dest; + dest->pub.init_destination = init_mem_destination; + dest->pub.empty_output_buffer = empty_mem_output_buffer; + dest->pub.term_destination = term_mem_destination; + dest->outbuffer = outbuffer; + dest->outsize = outsize; + if (*outbuffer == NULL || *outsize == 0) { + /* Allocate initial buffer */ + *outbuffer = malloc(OUTPUT_BUF_SIZE); + if (*outbuffer == NULL) + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10); + *outsize = OUTPUT_BUF_SIZE; + } + + dest->pub.next_output_byte = dest->buffer = *outbuffer; + dest->pub.free_in_buffer = dest->bufsize = *outsize; +} +/* end of code from libjpeg */ + +static inline uint32_t mjpeg_encoder_get_source_fps(MJpegEncoder *encoder) +{ + return encoder->cbs.get_source_fps ? + encoder->cbs.get_source_fps(encoder->cbs_opaque) : MJPEG_MAX_FPS; +} + +static inline uint32_t mjpeg_encoder_get_latency(MJpegEncoder *encoder) +{ + return encoder->cbs.get_roundtrip_ms ? + encoder->cbs.get_roundtrip_ms(encoder->cbs_opaque) / 2 : 0; +} + +static uint32_t get_max_fps(uint64_t frame_size, uint64_t bytes_per_sec) +{ + double fps; + double send_time_ms; + + if (!bytes_per_sec) { + return 0; + } + send_time_ms = frame_size * 1000.0 / bytes_per_sec; + fps = send_time_ms ? 1000 / send_time_ms : MJPEG_MAX_FPS; + return fps; +} + +static inline void mjpeg_encoder_reset_quality(MJpegEncoder *encoder, + int quality_id, + uint32_t fps, + uint64_t frame_enc_size) +{ + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + double fps_ratio; + + rate_control->during_quality_eval = FALSE; + + if (rate_control->quality_id != quality_id) { + rate_control->last_enc_size = 0; + } + + if (rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) { + memset(&rate_control->server_state, 0, sizeof(MJpegEncoderServerState)); + } + rate_control->quality_id = quality_id; + memset(&rate_control->quality_eval_data, 0, sizeof(MJpegEncoderQualityEval)); + rate_control->quality_eval_data.max_quality_id = MJPEG_QUALITY_SAMPLE_NUM - 1; + rate_control->quality_eval_data.max_quality_fps = MJPEG_MAX_FPS; + + if (rate_control->adjusted_fps) { + fps_ratio = rate_control->adjusted_fps / rate_control->fps; + } else { + fps_ratio = 1.5; + } + rate_control->fps = MAX(MJPEG_MIN_FPS, fps); + rate_control->fps = MIN(MJPEG_MAX_FPS, rate_control->fps); + rate_control->adjusted_fps = rate_control->fps*fps_ratio; + spice_debug("adjusted-fps-ratio=%.2f adjusted-fps=%.2f", fps_ratio, rate_control->adjusted_fps); + rate_control->adjusted_fps_start_time = 0; + rate_control->adjusted_fps_num_frames = 0; + rate_control->base_enc_size = frame_enc_size; + + rate_control->sum_recent_enc_size = 0; + rate_control->num_recent_enc_frames = 0; +} + +#define QUALITY_WAS_EVALUATED(encoder, quality) \ + ((encoder)->rate_control.quality_eval_data.encoded_size_by_quality[(quality)] != 0) + +/* + * Adjust the stream's jpeg quality and frame rate. + * We evaluate the compression ratio of different jpeg qualities; + * We compress successive frames with different qualities, + * and then we estimate the stream frame rate according to the currently + * evaluated jpeg quality and available bit rate. + * + * During quality evaluation, mjpeg_encoder_eval_quality is called before a new + * frame is encoded. mjpeg_encoder_eval_quality examines the encoding size of + * the previously encoded frame, and determines whether to continue evaluation + * (and chnages the quality for the frame that is going to be encoded), + * or stop evaluation (and sets the quality and frame rate for the stream). + * When qualities are scanned, we assume monotonicity of compression ratio + * as a function of jpeg quality. When we reach a quality with too small, or + * big enough compression ratio, we stop the evaluation and set the stream parameters. +*/ +static inline void mjpeg_encoder_eval_quality(MJpegEncoder *encoder) +{ + MJpegEncoderRateControl *rate_control; + MJpegEncoderQualityEval *quality_eval; + uint32_t fps, src_fps; + uint64_t enc_size; + uint32_t final_quality_id; + uint32_t final_fps; + uint64_t final_quality_enc_size; + + rate_control = &encoder->rate_control; + quality_eval = &rate_control->quality_eval_data; + + spice_assert(rate_control->during_quality_eval); + + /* retrieving the encoded size of the last encoded frame */ + enc_size = quality_eval->encoded_size_by_quality[rate_control->quality_id]; + if (enc_size == 0) { + spice_debug("size info missing"); + return; + } + + src_fps = mjpeg_encoder_get_source_fps(encoder); + + fps = get_max_fps(enc_size, rate_control->byte_rate); + spice_debug("mjpeg %p: jpeg %d: %.2f (KB) fps %d src-fps %u", + encoder, + mjpeg_quality_samples[rate_control->quality_id], + enc_size / 1024.0, + fps, + src_fps); + + if (fps > quality_eval->max_sampled_fps || + ((fps == quality_eval->max_sampled_fps || fps >= src_fps) && + rate_control->quality_id > quality_eval->max_sampled_fps_quality_id)) { + quality_eval->max_sampled_fps = fps; + quality_eval->max_sampled_fps_quality_id = rate_control->quality_id; + } + + /* + * Choosing whether to evaluate another quality, or to complete evaluation + * and set the stream parameters according to one of the qualities that + * were already sampled. + */ + + if (rate_control->quality_id > MJPEG_QUALITY_SAMPLE_NUM / 2 && + fps < MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH && + fps < src_fps) { + /* + * When the jpeg quality is bigger than the median quality, prefer a reasonable + * frame rate over improving the quality + */ + spice_debug("fps < %d && (fps < src_fps), quality %d", + MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH, + mjpeg_quality_samples[rate_control->quality_id]); + if (QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id - 1)) { + /* the next worse quality was already evaluated and it passed the frame + * rate thresholds (we know that, because we continued evaluating a better + * quality) */ + rate_control->quality_id--; + goto complete_sample; + } else { + /* evaluate the next worse quality */ + rate_control->quality_id--; + } + } else if ((fps > MJPEG_IMPROVE_QUALITY_FPS_PERMISSIVE_TH && + fps >= 0.66 * quality_eval->min_quality_fps) || fps >= src_fps) { + /* When the jpeg quality is worse than the median one (see first condition), we allow a less + strict threshold for fps, in order to improve the jpeg quality */ + if (rate_control->quality_id + 1 == MJPEG_QUALITY_SAMPLE_NUM || + rate_control->quality_id >= quality_eval->max_quality_id || + QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id + 1)) { + /* best quality has been reached, or the next (better) quality was + * already evaluated and didn't pass the fps thresholds */ + goto complete_sample; + } else { + if (rate_control->quality_id == MJPEG_QUALITY_SAMPLE_NUM / 2 && + fps < MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH && + fps < src_fps) { + goto complete_sample; + } + /* evaluate the next quality as well*/ + rate_control->quality_id++; + } + } else { // very small frame rate, try to improve by downgrading the quality + if (rate_control->quality_id == 0 || + rate_control->quality_id <= quality_eval->min_quality_id) { + goto complete_sample; + } else if (QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id - 1)) { + rate_control->quality_id--; + goto complete_sample; + } else { + /* evaluate the next worse quality */ + rate_control->quality_id--; + } + } + return; + +complete_sample: + if (quality_eval->max_sampled_fps != 0) { + /* covering a case were monotonicity was violated and we sampled + a better jepg quality, with better frame rate. */ + final_quality_id = MAX(rate_control->quality_id, + quality_eval->max_sampled_fps_quality_id); + } else { + final_quality_id = rate_control->quality_id; + } + final_quality_enc_size = quality_eval->encoded_size_by_quality[final_quality_id]; + final_fps = get_max_fps(final_quality_enc_size, + rate_control->byte_rate); + + if (final_quality_id == quality_eval->min_quality_id) { + final_fps = MAX(final_fps, quality_eval->min_quality_fps); + } + if (final_quality_id == quality_eval->max_quality_id) { + final_fps = MIN(final_fps, quality_eval->max_quality_fps); + } + mjpeg_encoder_reset_quality(encoder, final_quality_id, final_fps, final_quality_enc_size); + rate_control->sum_recent_enc_size = final_quality_enc_size; + rate_control->num_recent_enc_frames = 1; + + spice_debug("MJpeg quality sample end %p: quality %d fps %d", + encoder, mjpeg_quality_samples[rate_control->quality_id], rate_control->fps); + if (encoder->cbs.update_client_playback_delay) { + uint32_t latency = mjpeg_encoder_get_latency(encoder); + uint32_t min_delay = get_min_required_playback_delay(final_quality_enc_size, + rate_control->byte_rate, + latency); + + encoder->cbs.update_client_playback_delay(encoder->cbs_opaque, min_delay); + } +} + +static void mjpeg_encoder_quality_eval_set_upgrade(MJpegEncoder *encoder, + int reason, + uint32_t min_quality_id, + uint32_t min_quality_fps) +{ + MJpegEncoderQualityEval *quality_eval = &encoder->rate_control.quality_eval_data; + + encoder->rate_control.during_quality_eval = TRUE; + quality_eval->type = MJPEG_QUALITY_EVAL_TYPE_UPGRADE; + quality_eval->reason = reason; + quality_eval->min_quality_id = min_quality_id; + quality_eval->min_quality_fps = min_quality_fps; +} + +static void mjpeg_encoder_quality_eval_set_downgrade(MJpegEncoder *encoder, + int reason, + uint32_t max_quality_id, + uint32_t max_quality_fps) +{ + MJpegEncoderQualityEval *quality_eval = &encoder->rate_control.quality_eval_data; + + encoder->rate_control.during_quality_eval = TRUE; + quality_eval->type = MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE; + quality_eval->reason = reason; + quality_eval->max_quality_id = max_quality_id; + quality_eval->max_quality_fps = max_quality_fps; +} + +static void mjpeg_encoder_adjust_params_to_bit_rate(MJpegEncoder *encoder) +{ + MJpegEncoderRateControl *rate_control; + MJpegEncoderQualityEval *quality_eval; + uint64_t new_avg_enc_size = 0; + uint32_t new_fps; + uint32_t latency = 0; + uint32_t src_fps; + + spice_assert(rate_control_is_active(encoder)); + + rate_control = &encoder->rate_control; + quality_eval = &rate_control->quality_eval_data; + + if (!rate_control->last_enc_size) { + spice_debug("missing sample size"); + return; + } + + if (rate_control->during_quality_eval) { + quality_eval->encoded_size_by_quality[rate_control->quality_id] = rate_control->last_enc_size; + mjpeg_encoder_eval_quality(encoder); + return; + } + + if (!rate_control->num_recent_enc_frames) { + spice_debug("No recent encoded frames"); + return; + } + + if (rate_control->num_recent_enc_frames < MJPEG_AVERAGE_SIZE_WINDOW && + rate_control->num_recent_enc_frames < rate_control->fps) { + goto end; + } + + latency = mjpeg_encoder_get_latency(encoder); + new_avg_enc_size = rate_control->sum_recent_enc_size / + rate_control->num_recent_enc_frames; + new_fps = get_max_fps(new_avg_enc_size, rate_control->byte_rate); + + spice_debug("cur-fps=%u new-fps=%u (new/old=%.2f) |" + "bit-rate=%.2f (Mbps) latency=%u (ms) quality=%d |" + " new-size-avg %"PRIu64" , base-size %"PRIu64", (new/old=%.2f) ", + rate_control->fps, new_fps, ((double)new_fps)/rate_control->fps, + ((double)rate_control->byte_rate*8)/1024/1024, + latency, + mjpeg_quality_samples[rate_control->quality_id], + new_avg_enc_size, rate_control->base_enc_size, + rate_control->base_enc_size ? + ((double)new_avg_enc_size) / rate_control->base_enc_size : + 1); + + src_fps = mjpeg_encoder_get_source_fps(encoder); + + /* + * The ratio between the new_fps and the current fps reflects the changes + * in latency and frame size. When the change passes a threshold, + * we re-evaluate the quality and frame rate. + */ + if (new_fps > rate_control->fps && + (rate_control->fps < src_fps || rate_control->quality_id < MJPEG_QUALITY_SAMPLE_NUM - 1)) { + spice_debug("mjpeg %p FPS CHANGE >> : re-evaluating params", encoder); + mjpeg_encoder_quality_eval_set_upgrade(encoder, MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE, + rate_control->quality_id, /* fps has improved --> + don't allow stream quality + to deteriorate */ + rate_control->fps); + } else if (new_fps < rate_control->fps && new_fps < src_fps) { + spice_debug("mjpeg %p FPS CHANGE << : re-evaluating params", encoder); + mjpeg_encoder_quality_eval_set_downgrade(encoder, MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE, + rate_control->quality_id, + rate_control->fps); + } +end: + if (rate_control->during_quality_eval) { + quality_eval->encoded_size_by_quality[rate_control->quality_id] = new_avg_enc_size; + mjpeg_encoder_eval_quality(encoder); + } else { + mjpeg_encoder_process_server_drops(encoder); + } +} + +/* + * The actual frames distribution does not necessarily fit the condition "at least + * one frame every (1000/rate_contorl->fps) milliseconds". + * For keeping the average fps close to the defined fps, we periodically + * measure the current average fps, and modify rate_control->adjusted_fps accordingly. + * Then, we use (1000/rate_control->adjusted_fps) as the interval between frames. + */ +static void mjpeg_encoder_adjust_fps(MJpegEncoder *encoder, uint64_t now) +{ + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + uint64_t adjusted_fps_time_passed; + + spice_assert(rate_control_is_active(encoder)); + + adjusted_fps_time_passed = (now - rate_control->adjusted_fps_start_time) / 1000 / 1000; + + if (!rate_control->during_quality_eval && + adjusted_fps_time_passed > MJPEG_ADJUST_FPS_TIMEOUT && + adjusted_fps_time_passed > 1000 / rate_control->adjusted_fps) { + double avg_fps; + double fps_ratio; + + avg_fps = ((double)rate_control->adjusted_fps_num_frames*1000) / + adjusted_fps_time_passed; + spice_debug("#frames-adjust=%"PRIu64" #adjust-time=%"PRIu64" avg-fps=%.2f", + rate_control->adjusted_fps_num_frames, adjusted_fps_time_passed, avg_fps); + spice_debug("defined=%u old-adjusted=%.2f", rate_control->fps, rate_control->adjusted_fps); + fps_ratio = avg_fps / rate_control->fps; + if (avg_fps + 0.5 < rate_control->fps && + mjpeg_encoder_get_source_fps(encoder) > avg_fps) { + double new_adjusted_fps = avg_fps ? + (rate_control->adjusted_fps/fps_ratio) : + rate_control->adjusted_fps * 2; + + rate_control->adjusted_fps = MIN(rate_control->fps*2, new_adjusted_fps); + spice_debug("new-adjusted-fps=%.2f", rate_control->adjusted_fps); + } else if (rate_control->fps + 0.5 < avg_fps) { + double new_adjusted_fps = rate_control->adjusted_fps / fps_ratio; + + rate_control->adjusted_fps = MAX(rate_control->fps, new_adjusted_fps); + spice_debug("new-adjusted-fps=%.2f", rate_control->adjusted_fps); + } + rate_control->adjusted_fps_start_time = now; + rate_control->adjusted_fps_num_frames = 0; + } +} + +/* + * dest must be either NULL or allocated by malloc, since it might be freed + * during the encoding, if its size is too small. + * + * return: + * MJPEG_ENCODER_FRAME_UNSUPPORTED : frame cannot be encoded + * MJPEG_ENCODER_FRAME_DROP : frame should be dropped. This value can only be returned + * if mjpeg rate control is active. + * MJPEG_ENCODER_FRAME_ENCODE_DONE : frame encoding started. Continue with + * mjpeg_encoder_encode_scanline. + */ +static int mjpeg_encoder_start_frame(MJpegEncoder *encoder, + SpiceBitmapFmt format, + int width, int height, + uint8_t **dest, size_t *dest_len, + uint32_t frame_mm_time) +{ + uint32_t quality; + + if (rate_control_is_active(encoder)) { + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + uint64_t now; + uint64_t interval; + + now = red_get_monotonic_time(); + + if (!rate_control->adjusted_fps_start_time) { + rate_control->adjusted_fps_start_time = now; + } + mjpeg_encoder_adjust_fps(encoder, now); + interval = (now - rate_control->bit_rate_info.last_frame_time); + + if (interval < (1000*1000*1000) / rate_control->adjusted_fps) { + return MJPEG_ENCODER_FRAME_DROP; + } + + mjpeg_encoder_adjust_params_to_bit_rate(encoder); + + if (!rate_control->during_quality_eval || + rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) { + MJpegEncoderBitRateInfo *bit_rate_info; + + bit_rate_info = &encoder->rate_control.bit_rate_info; + + if (!bit_rate_info->change_start_time) { + bit_rate_info->change_start_time = now; + bit_rate_info->change_start_mm_time = frame_mm_time; + } + bit_rate_info->last_frame_time = now; + } + } + + encoder->cinfo.in_color_space = JCS_RGB; + encoder->cinfo.input_components = 3; + encoder->pixel_converter = NULL; + + switch (format) { + case SPICE_BITMAP_FMT_32BIT: + case SPICE_BITMAP_FMT_RGBA: + encoder->bytes_per_pixel = 4; +#ifdef JCS_EXTENSIONS + encoder->cinfo.in_color_space = JCS_EXT_BGRX; + encoder->cinfo.input_components = 4; +#else + encoder->pixel_converter = pixel_rgb32bpp_to_24; +#endif + break; + case SPICE_BITMAP_FMT_16BIT: + encoder->bytes_per_pixel = 2; + encoder->pixel_converter = pixel_rgb16bpp_to_24; + break; + case SPICE_BITMAP_FMT_24BIT: + encoder->bytes_per_pixel = 3; +#ifdef JCS_EXTENSIONS + encoder->cinfo.in_color_space = JCS_EXT_BGR; +#else + encoder->pixel_converter = pixel_rgb24bpp_to_24; +#endif + break; + default: + spice_debug("unsupported format %d", format); + return MJPEG_ENCODER_FRAME_UNSUPPORTED; + } + + if (encoder->pixel_converter != NULL) { + unsigned int stride = width * 3; + /* check for integer overflow */ + if (stride < width) { + return MJPEG_ENCODER_FRAME_UNSUPPORTED; + } + if (encoder->row_size < stride) { + encoder->row = spice_realloc(encoder->row, stride); + encoder->row_size = stride; + } + } + + spice_jpeg_mem_dest(&encoder->cinfo, dest, dest_len); + + encoder->cinfo.image_width = width; + encoder->cinfo.image_height = height; + jpeg_set_defaults(&encoder->cinfo); + encoder->cinfo.dct_method = JDCT_IFAST; + quality = mjpeg_quality_samples[encoder->rate_control.quality_id]; + jpeg_set_quality(&encoder->cinfo, quality, TRUE); + jpeg_start_compress(&encoder->cinfo, encoder->first_frame); + + encoder->num_frames++; + encoder->avg_quality += quality; + return MJPEG_ENCODER_FRAME_ENCODE_DONE; +} + +static int mjpeg_encoder_encode_scanline(MJpegEncoder *encoder, + uint8_t *src_pixels, + size_t image_width) +{ + unsigned int scanlines_written; + uint8_t *row; + + row = encoder->row; + if (encoder->pixel_converter) { + unsigned int x; + for (x = 0; x < image_width; x++) { + /* src_pixels is expected to be 4 bytes aligned */ + encoder->pixel_converter(src_pixels, row); + row += 3; + src_pixels += encoder->bytes_per_pixel; + } + scanlines_written = jpeg_write_scanlines(&encoder->cinfo, &encoder->row, 1); + } else { + scanlines_written = jpeg_write_scanlines(&encoder->cinfo, &src_pixels, 1); + } + if (scanlines_written == 0) { /* Not enough space */ + jpeg_abort_compress(&encoder->cinfo); + encoder->rate_control.last_enc_size = 0; + return 0; + } + + return scanlines_written; +} + +static size_t mjpeg_encoder_end_frame(MJpegEncoder *encoder) +{ + mem_destination_mgr *dest = (mem_destination_mgr *) encoder->cinfo.dest; + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + + jpeg_finish_compress(&encoder->cinfo); + + encoder->first_frame = FALSE; + rate_control->last_enc_size = dest->pub.next_output_byte - dest->buffer; + rate_control->server_state.num_frames_encoded++; + + if (!rate_control->during_quality_eval || + rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) { + + if (!rate_control->during_quality_eval) { + if (rate_control->num_recent_enc_frames >= MJPEG_AVERAGE_SIZE_WINDOW) { + rate_control->num_recent_enc_frames = 0; + rate_control->sum_recent_enc_size = 0; + } + rate_control->sum_recent_enc_size += rate_control->last_enc_size; + rate_control->num_recent_enc_frames++; + rate_control->adjusted_fps_num_frames++; + } + rate_control->bit_rate_info.sum_enc_size += encoder->rate_control.last_enc_size; + rate_control->bit_rate_info.num_enc_frames++; + } + return encoder->rate_control.last_enc_size; +} + +static inline uint8_t *get_image_line(SpiceChunks *chunks, size_t *offset, + int *chunk_nr, int stride) +{ + uint8_t *ret; + SpiceChunk *chunk; + + chunk = &chunks->chunk[*chunk_nr]; + + if (*offset == chunk->len) { + if (*chunk_nr == chunks->num_chunks - 1) { + return NULL; /* Last chunk */ + } + *offset = 0; + (*chunk_nr)++; + chunk = &chunks->chunk[*chunk_nr]; + } + + if (chunk->len - *offset < stride) { + spice_warning("bad chunk alignment"); + return NULL; + } + ret = chunk->data + *offset; + *offset += stride; + return ret; +} + +static int encode_frame(MJpegEncoder *encoder, const SpiceRect *src, + const SpiceBitmap *image, int top_down) +{ + SpiceChunks *chunks; + uint32_t image_stride; + size_t offset; + int i, chunk; + + chunks = image->data; + offset = 0; + chunk = 0; + image_stride = image->stride; + + const int skip_lines = top_down ? src->top : image->y - (src->bottom - 0); + for (i = 0; i < skip_lines; i++) { + get_image_line(chunks, &offset, &chunk, image_stride); + } + + const unsigned int stream_height = src->bottom - src->top; + const unsigned int stream_width = src->right - src->left; + + for (i = 0; i < stream_height; i++) { + uint8_t *src_line = get_image_line(chunks, &offset, &chunk, image_stride); + + if (!src_line) { + return FALSE; + } + + src_line += src->left * mjpeg_encoder_get_bytes_per_pixel(encoder); + if (mjpeg_encoder_encode_scanline(encoder, src_line, stream_width) == 0) { + return FALSE; + } + } + + return TRUE; +} + +int mjpeg_encoder_encode_frame(MJpegEncoder *encoder, + const SpiceBitmap *bitmap, int width, int height, + const SpiceRect *src, + int top_down, uint32_t frame_mm_time, + uint8_t **outbuf, size_t *outbuf_size, + int *data_size) +{ + int ret = mjpeg_encoder_start_frame(encoder, bitmap->format, + width, height, outbuf, outbuf_size, + frame_mm_time); + if (ret != MJPEG_ENCODER_FRAME_ENCODE_DONE) { + return ret; + } + + if (!encode_frame(encoder, src, bitmap, top_down)) { + return MJPEG_ENCODER_FRAME_UNSUPPORTED; + } + + *data_size = mjpeg_encoder_end_frame(encoder); + + return MJPEG_ENCODER_FRAME_ENCODE_DONE; +} + + +static void mjpeg_encoder_quality_eval_stop(MJpegEncoder *encoder) +{ + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + uint32_t quality_id; + uint32_t fps; + + if (!rate_control->during_quality_eval) { + return; + } + switch (rate_control->quality_eval_data.type) { + case MJPEG_QUALITY_EVAL_TYPE_UPGRADE: + quality_id = rate_control->quality_eval_data.min_quality_id; + fps = rate_control->quality_eval_data.min_quality_fps; + break; + case MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE: + quality_id = rate_control->quality_eval_data.max_quality_id; + fps = rate_control->quality_eval_data.max_quality_fps; + break; + case MJPEG_QUALITY_EVAL_TYPE_SET: + quality_id = MJPEG_QUALITY_SAMPLE_NUM / 2; + fps = MJPEG_MAX_FPS / 2; + break; + default: + spice_warning("unexected"); + return; + } + mjpeg_encoder_reset_quality(encoder, quality_id, fps, 0); + spice_debug("during quality evaluation: canceling." + "reset quality to %d fps %d", + mjpeg_quality_samples[rate_control->quality_id], rate_control->fps); +} + +static void mjpeg_encoder_decrease_bit_rate(MJpegEncoder *encoder) +{ + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info; + uint64_t measured_byte_rate; + uint32_t measured_fps; + uint64_t decrease_size; + + mjpeg_encoder_quality_eval_stop(encoder); + + rate_control->client_state.max_video_latency = 0; + rate_control->client_state.max_audio_latency = 0; + if (rate_control->warmup_start_time) { + uint64_t now; + + now = red_get_monotonic_time(); + if (now - rate_control->warmup_start_time < MJPEG_WARMUP_TIME*1000*1000) { + spice_debug("during warmup. ignoring"); + return; + } else { + rate_control->warmup_start_time = 0; + } + } + + if (bit_rate_info->num_enc_frames > MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES || + bit_rate_info->num_enc_frames > rate_control->fps) { + double duration_sec; + + duration_sec = (bit_rate_info->last_frame_time - bit_rate_info->change_start_time); + duration_sec /= (1000.0 * 1000.0 * 1000.0); + measured_byte_rate = bit_rate_info->sum_enc_size / duration_sec; + measured_fps = bit_rate_info->num_enc_frames / duration_sec; + decrease_size = bit_rate_info->sum_enc_size / bit_rate_info->num_enc_frames; + spice_debug("bit rate esitimation %.2f (Mbps) fps %u", + measured_byte_rate*8/1024.0/1024, + measured_fps); + } else { + measured_byte_rate = rate_control->byte_rate; + measured_fps = rate_control->fps; + decrease_size = measured_byte_rate/measured_fps; + spice_debug("bit rate not re-estimated %.2f (Mbps) fps %u", + measured_byte_rate*8/1024.0/1024, + measured_fps); + } + + measured_byte_rate = MIN(rate_control->byte_rate, measured_byte_rate); + + if (decrease_size >= measured_byte_rate) { + decrease_size = measured_byte_rate / 2; + } + + rate_control->byte_rate = measured_byte_rate - decrease_size; + bit_rate_info->change_start_time = 0; + bit_rate_info->change_start_mm_time = 0; + bit_rate_info->last_frame_time = 0; + bit_rate_info->num_enc_frames = 0; + bit_rate_info->sum_enc_size = 0; + bit_rate_info->was_upgraded = FALSE; + + spice_debug("decrease bit rate %.2f (Mbps)", rate_control->byte_rate * 8 / 1024.0/1024.0); + mjpeg_encoder_quality_eval_set_downgrade(encoder, + MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE, + rate_control->quality_id, + rate_control->fps); +} + +static void mjpeg_encoder_handle_negative_client_stream_report(MJpegEncoder *encoder, + uint32_t report_end_frame_mm_time) +{ + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + + spice_debug(NULL); + + if ((rate_control->bit_rate_info.change_start_mm_time > report_end_frame_mm_time || + !rate_control->bit_rate_info.change_start_mm_time) && + !rate_control->bit_rate_info.was_upgraded) { + spice_debug("ignoring, a downgrade has already occurred later to the report time"); + return; + } + + mjpeg_encoder_decrease_bit_rate(encoder); +} + +static void mjpeg_encoder_increase_bit_rate(MJpegEncoder *encoder) +{ + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info; + uint64_t measured_byte_rate; + uint32_t measured_fps; + uint64_t increase_size; + + + if (bit_rate_info->num_enc_frames > MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES || + bit_rate_info->num_enc_frames > rate_control->fps) { + uint64_t avg_frame_size; + double duration_sec; + + duration_sec = (bit_rate_info->last_frame_time - bit_rate_info->change_start_time); + duration_sec /= (1000.0 * 1000.0 * 1000.0); + measured_byte_rate = bit_rate_info->sum_enc_size / duration_sec; + measured_fps = bit_rate_info->num_enc_frames / duration_sec; + avg_frame_size = bit_rate_info->sum_enc_size / bit_rate_info->num_enc_frames; + spice_debug("bit rate esitimation %.2f (Mbps) defined %.2f" + " fps %u avg-frame-size=%.2f (KB)", + measured_byte_rate*8/1024.0/1024, + rate_control->byte_rate*8/1024.0/1024, + measured_fps, + avg_frame_size/1024.0); + increase_size = avg_frame_size; + } else { + spice_debug("not enough samples for measuring the bit rate. no change"); + return; + } + + + mjpeg_encoder_quality_eval_stop(encoder); + + if (measured_byte_rate + increase_size < rate_control->byte_rate) { + spice_debug("measured byte rate is small: not upgrading, just re-evaluating"); + } else { + rate_control->byte_rate = MIN(measured_byte_rate, rate_control->byte_rate) + increase_size; + } + + bit_rate_info->change_start_time = 0; + bit_rate_info->change_start_mm_time = 0; + bit_rate_info->last_frame_time = 0; + bit_rate_info->num_enc_frames = 0; + bit_rate_info->sum_enc_size = 0; + bit_rate_info->was_upgraded = TRUE; + + spice_debug("increase bit rate %.2f (Mbps)", rate_control->byte_rate * 8 / 1024.0/1024.0); + mjpeg_encoder_quality_eval_set_upgrade(encoder, + MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE, + rate_control->quality_id, + rate_control->fps); +} + +static void mjpeg_encoder_handle_positive_client_stream_report(MJpegEncoder *encoder, + uint32_t report_start_frame_mm_time) +{ + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info; + int stable_client_mm_time; + int timeout; + + if (rate_control->during_quality_eval && + rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) { + spice_debug("during quality evaluation (rate change). ignoring report"); + return; + } + + if ((rate_control->fps > MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH || + rate_control->fps >= mjpeg_encoder_get_source_fps(encoder)) && + rate_control->quality_id > MJPEG_QUALITY_SAMPLE_NUM / 2) { + timeout = MJPEG_CLIENT_POSITIVE_REPORT_STRICT_TIMEOUT; + } else { + timeout = MJPEG_CLIENT_POSITIVE_REPORT_TIMEOUT; + } + + stable_client_mm_time = (int)report_start_frame_mm_time - bit_rate_info->change_start_mm_time; + + if (!bit_rate_info->change_start_mm_time || stable_client_mm_time < timeout) { + /* assessing the stability of the current setting and only then + * respond to the report */ + spice_debug("no drops, but not enough time has passed for assessing" + "the playback stability since the last bit rate change"); + return; + } + mjpeg_encoder_increase_bit_rate(encoder); +} + +/* + * the video playback jitter buffer should be at least (send_time*2 + net_latency) for + * preventing underflow + */ +static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size, + uint64_t byte_rate, + uint32_t latency) +{ + uint32_t one_frame_time; + uint32_t min_delay; + + if (!frame_enc_size || !byte_rate) { + return latency; + } + one_frame_time = (frame_enc_size*1000)/byte_rate; + + min_delay = MIN(one_frame_time*2 + latency, MJPEG_MAX_CLIENT_PLAYBACK_DELAY); + return min_delay; +} + +#define MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR 0.5 +#define MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR 1.25 +#define MJPEG_VIDEO_DELAY_TH -15 + +void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder, + uint32_t num_frames, + uint32_t num_drops, + uint32_t start_frame_mm_time, + uint32_t end_frame_mm_time, + int32_t end_frame_delay, + uint32_t audio_delay) +{ + MJpegEncoderRateControl *rate_control = &encoder->rate_control; + MJpegEncoderClientState *client_state = &rate_control->client_state; + uint64_t avg_enc_size = 0; + uint32_t min_playback_delay; + int is_video_delay_small = FALSE; + + spice_debug("client report: #frames %u, #drops %d, duration %u video-delay %d audio-delay %u", + num_frames, num_drops, + end_frame_mm_time - start_frame_mm_time, + end_frame_delay, audio_delay); + + if (!rate_control_is_active(encoder)) { + spice_debug("rate control was not activated: ignoring"); + return; + } + if (rate_control->during_quality_eval) { + if (rate_control->quality_eval_data.type == MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE && + rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) { + spice_debug("during rate downgrade evaluation"); + return; + } + } + + if (rate_control->num_recent_enc_frames) { + avg_enc_size = rate_control->sum_recent_enc_size / + rate_control->num_recent_enc_frames; + } + spice_debug("recent size avg %.2f (KB)", avg_enc_size / 1024.0); + min_playback_delay = get_min_required_playback_delay(avg_enc_size, rate_control->byte_rate, + mjpeg_encoder_get_latency(encoder)); + spice_debug("min-delay %u client-delay %d", min_playback_delay, end_frame_delay); + + if (min_playback_delay > end_frame_delay) { + uint32_t src_fps = mjpeg_encoder_get_source_fps(encoder); + /* + * if the stream is at its highest rate, we can't estimate the "real" + * network bit rate and the min_playback_delay + */ + if (rate_control->quality_id != MJPEG_QUALITY_SAMPLE_NUM - 1 || + rate_control->fps < MIN(src_fps, MJPEG_MAX_FPS) || end_frame_delay < 0) { + is_video_delay_small = TRUE; + if (encoder->cbs.update_client_playback_delay) { + encoder->cbs.update_client_playback_delay(encoder->cbs_opaque, + min_playback_delay); + } + } + } + + + /* + * If the audio latency has decreased (since the start of the current + * sequence of positive reports), and the video latency is bigger, slow down + * the video rate + */ + if (end_frame_delay > 0 && + audio_delay < MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR*client_state->max_audio_latency && + end_frame_delay > MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR*audio_delay) { + spice_debug("video_latency >> audio_latency && audio_latency << max (%u)", + client_state->max_audio_latency); + mjpeg_encoder_handle_negative_client_stream_report(encoder, + end_frame_mm_time); + return; + } + + if (end_frame_delay < MJPEG_VIDEO_DELAY_TH) { + mjpeg_encoder_handle_negative_client_stream_report(encoder, + end_frame_mm_time); + } else { + double major_delay_decrease_thresh; + double medium_delay_decrease_thresh; + + client_state->max_video_latency = MAX(end_frame_delay, client_state->max_video_latency); + client_state->max_audio_latency = MAX(audio_delay, client_state->max_audio_latency); + + medium_delay_decrease_thresh = client_state->max_video_latency; + medium_delay_decrease_thresh *= MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR; + + major_delay_decrease_thresh = medium_delay_decrease_thresh; + major_delay_decrease_thresh *= MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR; + /* + * since the bit rate and the required latency are only evaluation based on the + * reports we got till now, we assume that the latency is too low only if it + * was higher during the time that passed since the last report that resulted + * in a bit rate decrement. If we find that the latency has decreased, it might + * suggest that the stream bit rate is too high. + */ + if ((end_frame_delay < medium_delay_decrease_thresh && + is_video_delay_small) || end_frame_delay < major_delay_decrease_thresh) { + spice_debug("downgrade due to short video delay (last=%u, past-max=%u", + end_frame_delay, client_state->max_video_latency); + mjpeg_encoder_handle_negative_client_stream_report(encoder, + end_frame_mm_time); + } else if (!num_drops) { + mjpeg_encoder_handle_positive_client_stream_report(encoder, + start_frame_mm_time); + + } + } +} + +void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder) +{ + encoder->rate_control.server_state.num_frames_dropped++; + mjpeg_encoder_process_server_drops(encoder); +} + +/* + * decrease the bit rate if the drop rate on the sever side exceeds a pre defined + * threshold. + */ +static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder) +{ + MJpegEncoderServerState *server_state = &encoder->rate_control.server_state; + uint32_t num_frames_total; + double drop_factor; + uint32_t fps; + + fps = MIN(encoder->rate_control.fps, mjpeg_encoder_get_source_fps(encoder)); + if (server_state->num_frames_encoded < fps * MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL) { + return; + } + + num_frames_total = server_state->num_frames_dropped + server_state->num_frames_encoded; + drop_factor = ((double)server_state->num_frames_dropped) / num_frames_total; + + spice_debug("#drops %u total %u fps %u src-fps %u", + server_state->num_frames_dropped, + num_frames_total, + encoder->rate_control.fps, + mjpeg_encoder_get_source_fps(encoder)); + + if (drop_factor > MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH) { + mjpeg_encoder_decrease_bit_rate(encoder); + } + server_state->num_frames_encoded = 0; + server_state->num_frames_dropped = 0; +} + +uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder) +{ + return encoder->rate_control.byte_rate * 8; +} + +void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats) +{ + spice_assert(encoder != NULL && stats != NULL); + stats->starting_bit_rate = encoder->starting_bit_rate; + stats->cur_bit_rate = mjpeg_encoder_get_bit_rate(encoder); + stats->avg_quality = (double)encoder->avg_quality / encoder->num_frames; +} + +MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate, + MJpegEncoderRateControlCbs *cbs, + void *cbs_opaque) +{ + MJpegEncoder *encoder = spice_new0(MJpegEncoder, 1); + + encoder->first_frame = TRUE; + encoder->rate_control.byte_rate = starting_bit_rate / 8; + encoder->starting_bit_rate = starting_bit_rate; + + if (cbs) { + struct timespec time; + + clock_gettime(CLOCK_MONOTONIC, &time); + encoder->cbs = *cbs; + encoder->cbs_opaque = cbs_opaque; + mjpeg_encoder_reset_quality(encoder, MJPEG_QUALITY_SAMPLE_NUM / 2, 5, 0); + encoder->rate_control.during_quality_eval = TRUE; + encoder->rate_control.quality_eval_data.type = MJPEG_QUALITY_EVAL_TYPE_SET; + encoder->rate_control.quality_eval_data.reason = MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE; + encoder->rate_control.warmup_start_time = ((uint64_t) time.tv_sec) * 1000000000 + time.tv_nsec; + } else { + encoder->cbs.get_roundtrip_ms = NULL; + mjpeg_encoder_reset_quality(encoder, MJPEG_LEGACY_STATIC_QUALITY_ID, MJPEG_MAX_FPS, 0); + } + + encoder->cinfo.err = jpeg_std_error(&encoder->jerr); + jpeg_create_compress(&encoder->cinfo); + + return encoder; +} diff --git a/server/mjpeg-encoder.h b/server/mjpeg-encoder.h new file mode 100644 index 0000000..d070e70 --- /dev/null +++ b/server/mjpeg-encoder.h @@ -0,0 +1,100 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _H_MJPEG_ENCODER +#define _H_MJPEG_ENCODER + +#include "red_common.h" + +enum { + MJPEG_ENCODER_FRAME_UNSUPPORTED = -1, + MJPEG_ENCODER_FRAME_DROP, + MJPEG_ENCODER_FRAME_ENCODE_DONE, +}; + +typedef struct MJpegEncoder MJpegEncoder; + +/* + * Callbacks required for controling and adjusting + * the stream bit rate: + * get_roundtrip_ms: roundtrip time in milliseconds + * get_source_fps: the input frame rate (#frames per second), i.e., + * the rate of frames arriving from the guest to spice-server, + * before any drops. + */ +typedef struct MJpegEncoderRateControlCbs { + uint32_t (*get_roundtrip_ms)(void *opaque); + uint32_t (*get_source_fps)(void *opaque); + void (*update_client_playback_delay)(void *opaque, uint32_t delay_ms); +} MJpegEncoderRateControlCbs; + +typedef struct MJpegEncoderStats { + uint64_t starting_bit_rate; + uint64_t cur_bit_rate; + double avg_quality; +} MJpegEncoderStats; + +MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate, + MJpegEncoderRateControlCbs *cbs, void *opaque); +void mjpeg_encoder_destroy(MJpegEncoder *encoder); + +int mjpeg_encoder_encode_frame(MJpegEncoder *encoder, + const SpiceBitmap *bitmap, int width, int height, + const SpiceRect *src, + int top_down, uint32_t frame_mm_time, + uint8_t **outbuf, size_t *outbuf_size, + int *data_size); + +/* + * bit rate control + */ + +/* + * Data that should be periodically obtained from the client. The report contains: + * num_frames : the number of frames that reached the client during the time + * the report is referring to. + * num_drops : the part of the above frames that was dropped by the client due to + * late arrival time. + * start_frame_mm_time: the mm_time of the first frame included in the report + * end_frame_mm_time : the mm_time of the last_frame included in the report + * end_frame_delay : (end_frame_mm_time - client_mm_time) + * audio delay : the latency of the audio playback. + * If there is no audio playback, set it to MAX_UINT. + * + */ +void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder, + uint32_t num_frames, + uint32_t num_drops, + uint32_t start_frame_mm_time, + uint32_t end_frame_mm_time, + int32_t end_frame_delay, + uint32_t audio_delay); + +/* + * Notify the encoder each time a frame is dropped due to pipe + * congestion. + * We can deduce the client state by the frame dropping rate in the server. + * Monitoring the frame drops can help in fine tuning the playback parameters + * when the client reports are delayed. + */ +void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder); + +uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder); +void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats); + +#endif diff --git a/server/mjpeg_encoder.c b/server/mjpeg_encoder.c deleted file mode 100644 index 9b331c1..0000000 --- a/server/mjpeg_encoder.c +++ /dev/null @@ -1,1375 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include "red_common.h" -#include "mjpeg_encoder.h" -#include "utils.h" -#include <jerror.h> -#include <jpeglib.h> -#include <inttypes.h> - -#define MJPEG_MAX_FPS 25 -#define MJPEG_MIN_FPS 1 - -#define MJPEG_QUALITY_SAMPLE_NUM 7 -static const int mjpeg_quality_samples[MJPEG_QUALITY_SAMPLE_NUM] = {20, 30, 40, 50, 60, 70, 80}; - -#define MJPEG_LEGACY_STATIC_QUALITY_ID 5 // jpeg quality 70 - -#define MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH 10 -#define MJPEG_IMPROVE_QUALITY_FPS_PERMISSIVE_TH 5 - -#define MJPEG_AVERAGE_SIZE_WINDOW 3 - -#define MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES 3 -#define MJPEG_LOW_FPS_RATE_TH 3 - -#define MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL 1 -#define MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH 0.1 - -/* - * acting on positive client reports only if enough frame mm time - * has passed since the last bit rate change and the report. - * time - */ -#define MJPEG_CLIENT_POSITIVE_REPORT_TIMEOUT 2000 -#define MJPEG_CLIENT_POSITIVE_REPORT_STRICT_TIMEOUT 3000 - -#define MJPEG_ADJUST_FPS_TIMEOUT 500 - -/* - * avoid interrupting the playback when there are temporary - * incidents of instability (with respect to server and client drops) - */ -#define MJPEG_MAX_CLIENT_PLAYBACK_DELAY 5000 // 5 sec - -/* - * The stream starts after lossless frames were sent to the client, - * and without rate control (except for pipe congestion). Thus, on the beginning - * of the stream, we might observe frame drops on the client and server side which - * are not necessarily related to mis-estimation of the bit rate, and we would - * like to wait till the stream stabilizes. - */ -#define MJPEG_WARMUP_TIME 3000LL // 3 sec - -enum { - MJPEG_QUALITY_EVAL_TYPE_SET, - MJPEG_QUALITY_EVAL_TYPE_UPGRADE, - MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE, -}; - -enum { - MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE, - MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE, -}; - -typedef struct MJpegEncoderQualityEval { - int type; - int reason; - - uint64_t encoded_size_by_quality[MJPEG_QUALITY_SAMPLE_NUM]; - /* lower limit for the current evaluation round */ - int min_quality_id; - int min_quality_fps; // min fps for the given quality - /* upper limit for the current evaluation round */ - int max_quality_id; - int max_quality_fps; // max fps for the given quality - /* tracking the best sampled fps so far */ - int max_sampled_fps; - int max_sampled_fps_quality_id; -} MJpegEncoderQualityEval; - -typedef struct MJpegEncoderClientState { - int max_video_latency; - uint32_t max_audio_latency; -} MJpegEncoderClientState; - -typedef struct MJpegEncoderServerState { - uint32_t num_frames_encoded; - uint32_t num_frames_dropped; -} MJpegEncoderServerState; - -typedef struct MJpegEncoderBitRateInfo { - uint64_t change_start_time; - uint64_t last_frame_time; - uint32_t change_start_mm_time; - int was_upgraded; - - /* gathering data about the frames that - * were encoded since the last bit rate change*/ - uint32_t num_enc_frames; - uint64_t sum_enc_size; -} MJpegEncoderBitRateInfo; - -/* - * Adjusting the stream jpeg quality and frame rate (fps): - * When during_quality_eval=TRUE, we compress different frames with different - * jpeg quality. By considering (1) the resulting compression ratio, and (2) the available - * bit rate, we evaluate the max frame frequency for the stream with the given quality, - * and we choose the highest quality that will allow a reasonable frame rate. - * during_quality_eval is set for new streams and can also be set any time we want - * to re-evaluate the stream parameters (e.g., when the bit rate and/or - * compressed frame size significantly change). - */ -typedef struct MJpegEncoderRateControl { - int during_quality_eval; - MJpegEncoderQualityEval quality_eval_data; - MJpegEncoderBitRateInfo bit_rate_info; - MJpegEncoderClientState client_state; - MJpegEncoderServerState server_state; - - uint64_t byte_rate; - int quality_id; - uint32_t fps; - double adjusted_fps; - uint64_t adjusted_fps_start_time; - uint64_t adjusted_fps_num_frames; - - /* the encoded frame size which the quality and the fps evaluation was based upon */ - uint64_t base_enc_size; - - uint64_t last_enc_size; - - uint64_t sum_recent_enc_size; - uint32_t num_recent_enc_frames; - - uint64_t warmup_start_time; -} MJpegEncoderRateControl; - -struct MJpegEncoder { - uint8_t *row; - uint32_t row_size; - int first_frame; - - struct jpeg_compress_struct cinfo; - struct jpeg_error_mgr jerr; - - unsigned int bytes_per_pixel; /* bytes per pixel of the input buffer */ - void (*pixel_converter)(void *src, uint8_t *dest); - - MJpegEncoderRateControl rate_control; - MJpegEncoderRateControlCbs cbs; - void *cbs_opaque; - - /* stats */ - uint64_t starting_bit_rate; - uint64_t avg_quality; - uint32_t num_frames; -}; - -static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder); -static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size, - uint64_t byte_rate, - uint32_t latency); - -static inline int rate_control_is_active(MJpegEncoder* encoder) -{ - return encoder->cbs.get_roundtrip_ms != NULL; -} - -void mjpeg_encoder_destroy(MJpegEncoder *encoder) -{ - free(encoder->cinfo.dest); - jpeg_destroy_compress(&encoder->cinfo); - free(encoder->row); - free(encoder); -} - -static uint8_t mjpeg_encoder_get_bytes_per_pixel(MJpegEncoder *encoder) -{ - return encoder->bytes_per_pixel; -} - -#ifndef JCS_EXTENSIONS -/* Pixel conversion routines */ -static void pixel_rgb24bpp_to_24(void *src_ptr, uint8_t *dest) -{ - uint8_t *src = src_ptr; - /* libjpegs stores rgb, spice/win32 stores bgr */ - *dest++ = src[2]; /* red */ - *dest++ = src[1]; /* green */ - *dest++ = src[0]; /* blue */ -} - -static void pixel_rgb32bpp_to_24(void *src, uint8_t *dest) -{ - uint32_t pixel = *(uint32_t *)src; - *dest++ = (pixel >> 16) & 0xff; - *dest++ = (pixel >> 8) & 0xff; - *dest++ = (pixel >> 0) & 0xff; -} -#endif - -static void pixel_rgb16bpp_to_24(void *src, uint8_t *dest) -{ - uint16_t pixel = *(uint16_t *)src; - *dest++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7); - *dest++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7); - *dest++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7); -} - - -/* code from libjpeg 8 to handle compression to a memory buffer - * - * Copyright (C) 1994-1996, Thomas G. Lane. - * Modified 2009 by Guido Vollbeding. - * This file is part of the Independent JPEG Group's software. - */ -typedef struct { - struct jpeg_destination_mgr pub; /* public fields */ - - unsigned char ** outbuffer; /* target buffer */ - size_t * outsize; - uint8_t * buffer; /* start of buffer */ - size_t bufsize; -} mem_destination_mgr; - -static void init_mem_destination(j_compress_ptr cinfo) -{ -} - -static boolean empty_mem_output_buffer(j_compress_ptr cinfo) -{ - size_t nextsize; - uint8_t * nextbuffer; - mem_destination_mgr *dest = (mem_destination_mgr *) cinfo->dest; - - /* Try to allocate new buffer with double size */ - nextsize = dest->bufsize * 2; - nextbuffer = malloc(nextsize); - - if (nextbuffer == NULL) - ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10); - - memcpy(nextbuffer, dest->buffer, dest->bufsize); - - free(dest->buffer); - - dest->pub.next_output_byte = nextbuffer + dest->bufsize; - dest->pub.free_in_buffer = dest->bufsize; - - dest->buffer = nextbuffer; - dest->bufsize = nextsize; - - return TRUE; -} - -static void term_mem_destination(j_compress_ptr cinfo) -{ - mem_destination_mgr *dest = (mem_destination_mgr *) cinfo->dest; - - *dest->outbuffer = dest->buffer; - *dest->outsize = dest->bufsize; -} - -/* - * Prepare for output to a memory buffer. - * The caller may supply an own initial buffer with appropriate size. - * Otherwise, or when the actual data output exceeds the given size, - * the library adapts the buffer size as necessary. - * The standard library functions malloc/free are used for allocating - * larger memory, so the buffer is available to the application after - * finishing compression, and then the application is responsible for - * freeing the requested memory. - */ - -static void -spice_jpeg_mem_dest(j_compress_ptr cinfo, - unsigned char ** outbuffer, size_t * outsize) -{ - mem_destination_mgr *dest; -#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ - - if (outbuffer == NULL || outsize == NULL) /* sanity check */ - ERREXIT(cinfo, JERR_BUFFER_SIZE); - - /* The destination object is made permanent so that multiple JPEG images - * can be written to the same buffer without re-executing jpeg_mem_dest. - */ - if (cinfo->dest == NULL) { /* first time for this JPEG object? */ - cinfo->dest = spice_malloc(sizeof(mem_destination_mgr)); - } - - dest = (mem_destination_mgr *) cinfo->dest; - dest->pub.init_destination = init_mem_destination; - dest->pub.empty_output_buffer = empty_mem_output_buffer; - dest->pub.term_destination = term_mem_destination; - dest->outbuffer = outbuffer; - dest->outsize = outsize; - if (*outbuffer == NULL || *outsize == 0) { - /* Allocate initial buffer */ - *outbuffer = malloc(OUTPUT_BUF_SIZE); - if (*outbuffer == NULL) - ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10); - *outsize = OUTPUT_BUF_SIZE; - } - - dest->pub.next_output_byte = dest->buffer = *outbuffer; - dest->pub.free_in_buffer = dest->bufsize = *outsize; -} -/* end of code from libjpeg */ - -static inline uint32_t mjpeg_encoder_get_source_fps(MJpegEncoder *encoder) -{ - return encoder->cbs.get_source_fps ? - encoder->cbs.get_source_fps(encoder->cbs_opaque) : MJPEG_MAX_FPS; -} - -static inline uint32_t mjpeg_encoder_get_latency(MJpegEncoder *encoder) -{ - return encoder->cbs.get_roundtrip_ms ? - encoder->cbs.get_roundtrip_ms(encoder->cbs_opaque) / 2 : 0; -} - -static uint32_t get_max_fps(uint64_t frame_size, uint64_t bytes_per_sec) -{ - double fps; - double send_time_ms; - - if (!bytes_per_sec) { - return 0; - } - send_time_ms = frame_size * 1000.0 / bytes_per_sec; - fps = send_time_ms ? 1000 / send_time_ms : MJPEG_MAX_FPS; - return fps; -} - -static inline void mjpeg_encoder_reset_quality(MJpegEncoder *encoder, - int quality_id, - uint32_t fps, - uint64_t frame_enc_size) -{ - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - double fps_ratio; - - rate_control->during_quality_eval = FALSE; - - if (rate_control->quality_id != quality_id) { - rate_control->last_enc_size = 0; - } - - if (rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) { - memset(&rate_control->server_state, 0, sizeof(MJpegEncoderServerState)); - } - rate_control->quality_id = quality_id; - memset(&rate_control->quality_eval_data, 0, sizeof(MJpegEncoderQualityEval)); - rate_control->quality_eval_data.max_quality_id = MJPEG_QUALITY_SAMPLE_NUM - 1; - rate_control->quality_eval_data.max_quality_fps = MJPEG_MAX_FPS; - - if (rate_control->adjusted_fps) { - fps_ratio = rate_control->adjusted_fps / rate_control->fps; - } else { - fps_ratio = 1.5; - } - rate_control->fps = MAX(MJPEG_MIN_FPS, fps); - rate_control->fps = MIN(MJPEG_MAX_FPS, rate_control->fps); - rate_control->adjusted_fps = rate_control->fps*fps_ratio; - spice_debug("adjusted-fps-ratio=%.2f adjusted-fps=%.2f", fps_ratio, rate_control->adjusted_fps); - rate_control->adjusted_fps_start_time = 0; - rate_control->adjusted_fps_num_frames = 0; - rate_control->base_enc_size = frame_enc_size; - - rate_control->sum_recent_enc_size = 0; - rate_control->num_recent_enc_frames = 0; -} - -#define QUALITY_WAS_EVALUATED(encoder, quality) \ - ((encoder)->rate_control.quality_eval_data.encoded_size_by_quality[(quality)] != 0) - -/* - * Adjust the stream's jpeg quality and frame rate. - * We evaluate the compression ratio of different jpeg qualities; - * We compress successive frames with different qualities, - * and then we estimate the stream frame rate according to the currently - * evaluated jpeg quality and available bit rate. - * - * During quality evaluation, mjpeg_encoder_eval_quality is called before a new - * frame is encoded. mjpeg_encoder_eval_quality examines the encoding size of - * the previously encoded frame, and determines whether to continue evaluation - * (and chnages the quality for the frame that is going to be encoded), - * or stop evaluation (and sets the quality and frame rate for the stream). - * When qualities are scanned, we assume monotonicity of compression ratio - * as a function of jpeg quality. When we reach a quality with too small, or - * big enough compression ratio, we stop the evaluation and set the stream parameters. -*/ -static inline void mjpeg_encoder_eval_quality(MJpegEncoder *encoder) -{ - MJpegEncoderRateControl *rate_control; - MJpegEncoderQualityEval *quality_eval; - uint32_t fps, src_fps; - uint64_t enc_size; - uint32_t final_quality_id; - uint32_t final_fps; - uint64_t final_quality_enc_size; - - rate_control = &encoder->rate_control; - quality_eval = &rate_control->quality_eval_data; - - spice_assert(rate_control->during_quality_eval); - - /* retrieving the encoded size of the last encoded frame */ - enc_size = quality_eval->encoded_size_by_quality[rate_control->quality_id]; - if (enc_size == 0) { - spice_debug("size info missing"); - return; - } - - src_fps = mjpeg_encoder_get_source_fps(encoder); - - fps = get_max_fps(enc_size, rate_control->byte_rate); - spice_debug("mjpeg %p: jpeg %d: %.2f (KB) fps %d src-fps %u", - encoder, - mjpeg_quality_samples[rate_control->quality_id], - enc_size / 1024.0, - fps, - src_fps); - - if (fps > quality_eval->max_sampled_fps || - ((fps == quality_eval->max_sampled_fps || fps >= src_fps) && - rate_control->quality_id > quality_eval->max_sampled_fps_quality_id)) { - quality_eval->max_sampled_fps = fps; - quality_eval->max_sampled_fps_quality_id = rate_control->quality_id; - } - - /* - * Choosing whether to evaluate another quality, or to complete evaluation - * and set the stream parameters according to one of the qualities that - * were already sampled. - */ - - if (rate_control->quality_id > MJPEG_QUALITY_SAMPLE_NUM / 2 && - fps < MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH && - fps < src_fps) { - /* - * When the jpeg quality is bigger than the median quality, prefer a reasonable - * frame rate over improving the quality - */ - spice_debug("fps < %d && (fps < src_fps), quality %d", - MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH, - mjpeg_quality_samples[rate_control->quality_id]); - if (QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id - 1)) { - /* the next worse quality was already evaluated and it passed the frame - * rate thresholds (we know that, because we continued evaluating a better - * quality) */ - rate_control->quality_id--; - goto complete_sample; - } else { - /* evaluate the next worse quality */ - rate_control->quality_id--; - } - } else if ((fps > MJPEG_IMPROVE_QUALITY_FPS_PERMISSIVE_TH && - fps >= 0.66 * quality_eval->min_quality_fps) || fps >= src_fps) { - /* When the jpeg quality is worse than the median one (see first condition), we allow a less - strict threshold for fps, in order to improve the jpeg quality */ - if (rate_control->quality_id + 1 == MJPEG_QUALITY_SAMPLE_NUM || - rate_control->quality_id >= quality_eval->max_quality_id || - QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id + 1)) { - /* best quality has been reached, or the next (better) quality was - * already evaluated and didn't pass the fps thresholds */ - goto complete_sample; - } else { - if (rate_control->quality_id == MJPEG_QUALITY_SAMPLE_NUM / 2 && - fps < MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH && - fps < src_fps) { - goto complete_sample; - } - /* evaluate the next quality as well*/ - rate_control->quality_id++; - } - } else { // very small frame rate, try to improve by downgrading the quality - if (rate_control->quality_id == 0 || - rate_control->quality_id <= quality_eval->min_quality_id) { - goto complete_sample; - } else if (QUALITY_WAS_EVALUATED(encoder, rate_control->quality_id - 1)) { - rate_control->quality_id--; - goto complete_sample; - } else { - /* evaluate the next worse quality */ - rate_control->quality_id--; - } - } - return; - -complete_sample: - if (quality_eval->max_sampled_fps != 0) { - /* covering a case were monotonicity was violated and we sampled - a better jepg quality, with better frame rate. */ - final_quality_id = MAX(rate_control->quality_id, - quality_eval->max_sampled_fps_quality_id); - } else { - final_quality_id = rate_control->quality_id; - } - final_quality_enc_size = quality_eval->encoded_size_by_quality[final_quality_id]; - final_fps = get_max_fps(final_quality_enc_size, - rate_control->byte_rate); - - if (final_quality_id == quality_eval->min_quality_id) { - final_fps = MAX(final_fps, quality_eval->min_quality_fps); - } - if (final_quality_id == quality_eval->max_quality_id) { - final_fps = MIN(final_fps, quality_eval->max_quality_fps); - } - mjpeg_encoder_reset_quality(encoder, final_quality_id, final_fps, final_quality_enc_size); - rate_control->sum_recent_enc_size = final_quality_enc_size; - rate_control->num_recent_enc_frames = 1; - - spice_debug("MJpeg quality sample end %p: quality %d fps %d", - encoder, mjpeg_quality_samples[rate_control->quality_id], rate_control->fps); - if (encoder->cbs.update_client_playback_delay) { - uint32_t latency = mjpeg_encoder_get_latency(encoder); - uint32_t min_delay = get_min_required_playback_delay(final_quality_enc_size, - rate_control->byte_rate, - latency); - - encoder->cbs.update_client_playback_delay(encoder->cbs_opaque, min_delay); - } -} - -static void mjpeg_encoder_quality_eval_set_upgrade(MJpegEncoder *encoder, - int reason, - uint32_t min_quality_id, - uint32_t min_quality_fps) -{ - MJpegEncoderQualityEval *quality_eval = &encoder->rate_control.quality_eval_data; - - encoder->rate_control.during_quality_eval = TRUE; - quality_eval->type = MJPEG_QUALITY_EVAL_TYPE_UPGRADE; - quality_eval->reason = reason; - quality_eval->min_quality_id = min_quality_id; - quality_eval->min_quality_fps = min_quality_fps; -} - -static void mjpeg_encoder_quality_eval_set_downgrade(MJpegEncoder *encoder, - int reason, - uint32_t max_quality_id, - uint32_t max_quality_fps) -{ - MJpegEncoderQualityEval *quality_eval = &encoder->rate_control.quality_eval_data; - - encoder->rate_control.during_quality_eval = TRUE; - quality_eval->type = MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE; - quality_eval->reason = reason; - quality_eval->max_quality_id = max_quality_id; - quality_eval->max_quality_fps = max_quality_fps; -} - -static void mjpeg_encoder_adjust_params_to_bit_rate(MJpegEncoder *encoder) -{ - MJpegEncoderRateControl *rate_control; - MJpegEncoderQualityEval *quality_eval; - uint64_t new_avg_enc_size = 0; - uint32_t new_fps; - uint32_t latency = 0; - uint32_t src_fps; - - spice_assert(rate_control_is_active(encoder)); - - rate_control = &encoder->rate_control; - quality_eval = &rate_control->quality_eval_data; - - if (!rate_control->last_enc_size) { - spice_debug("missing sample size"); - return; - } - - if (rate_control->during_quality_eval) { - quality_eval->encoded_size_by_quality[rate_control->quality_id] = rate_control->last_enc_size; - mjpeg_encoder_eval_quality(encoder); - return; - } - - if (!rate_control->num_recent_enc_frames) { - spice_debug("No recent encoded frames"); - return; - } - - if (rate_control->num_recent_enc_frames < MJPEG_AVERAGE_SIZE_WINDOW && - rate_control->num_recent_enc_frames < rate_control->fps) { - goto end; - } - - latency = mjpeg_encoder_get_latency(encoder); - new_avg_enc_size = rate_control->sum_recent_enc_size / - rate_control->num_recent_enc_frames; - new_fps = get_max_fps(new_avg_enc_size, rate_control->byte_rate); - - spice_debug("cur-fps=%u new-fps=%u (new/old=%.2f) |" - "bit-rate=%.2f (Mbps) latency=%u (ms) quality=%d |" - " new-size-avg %"PRIu64" , base-size %"PRIu64", (new/old=%.2f) ", - rate_control->fps, new_fps, ((double)new_fps)/rate_control->fps, - ((double)rate_control->byte_rate*8)/1024/1024, - latency, - mjpeg_quality_samples[rate_control->quality_id], - new_avg_enc_size, rate_control->base_enc_size, - rate_control->base_enc_size ? - ((double)new_avg_enc_size) / rate_control->base_enc_size : - 1); - - src_fps = mjpeg_encoder_get_source_fps(encoder); - - /* - * The ratio between the new_fps and the current fps reflects the changes - * in latency and frame size. When the change passes a threshold, - * we re-evaluate the quality and frame rate. - */ - if (new_fps > rate_control->fps && - (rate_control->fps < src_fps || rate_control->quality_id < MJPEG_QUALITY_SAMPLE_NUM - 1)) { - spice_debug("mjpeg %p FPS CHANGE >> : re-evaluating params", encoder); - mjpeg_encoder_quality_eval_set_upgrade(encoder, MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE, - rate_control->quality_id, /* fps has improved --> - don't allow stream quality - to deteriorate */ - rate_control->fps); - } else if (new_fps < rate_control->fps && new_fps < src_fps) { - spice_debug("mjpeg %p FPS CHANGE << : re-evaluating params", encoder); - mjpeg_encoder_quality_eval_set_downgrade(encoder, MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE, - rate_control->quality_id, - rate_control->fps); - } -end: - if (rate_control->during_quality_eval) { - quality_eval->encoded_size_by_quality[rate_control->quality_id] = new_avg_enc_size; - mjpeg_encoder_eval_quality(encoder); - } else { - mjpeg_encoder_process_server_drops(encoder); - } -} - -/* - * The actual frames distribution does not necessarily fit the condition "at least - * one frame every (1000/rate_contorl->fps) milliseconds". - * For keeping the average fps close to the defined fps, we periodically - * measure the current average fps, and modify rate_control->adjusted_fps accordingly. - * Then, we use (1000/rate_control->adjusted_fps) as the interval between frames. - */ -static void mjpeg_encoder_adjust_fps(MJpegEncoder *encoder, uint64_t now) -{ - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - uint64_t adjusted_fps_time_passed; - - spice_assert(rate_control_is_active(encoder)); - - adjusted_fps_time_passed = (now - rate_control->adjusted_fps_start_time) / 1000 / 1000; - - if (!rate_control->during_quality_eval && - adjusted_fps_time_passed > MJPEG_ADJUST_FPS_TIMEOUT && - adjusted_fps_time_passed > 1000 / rate_control->adjusted_fps) { - double avg_fps; - double fps_ratio; - - avg_fps = ((double)rate_control->adjusted_fps_num_frames*1000) / - adjusted_fps_time_passed; - spice_debug("#frames-adjust=%"PRIu64" #adjust-time=%"PRIu64" avg-fps=%.2f", - rate_control->adjusted_fps_num_frames, adjusted_fps_time_passed, avg_fps); - spice_debug("defined=%u old-adjusted=%.2f", rate_control->fps, rate_control->adjusted_fps); - fps_ratio = avg_fps / rate_control->fps; - if (avg_fps + 0.5 < rate_control->fps && - mjpeg_encoder_get_source_fps(encoder) > avg_fps) { - double new_adjusted_fps = avg_fps ? - (rate_control->adjusted_fps/fps_ratio) : - rate_control->adjusted_fps * 2; - - rate_control->adjusted_fps = MIN(rate_control->fps*2, new_adjusted_fps); - spice_debug("new-adjusted-fps=%.2f", rate_control->adjusted_fps); - } else if (rate_control->fps + 0.5 < avg_fps) { - double new_adjusted_fps = rate_control->adjusted_fps / fps_ratio; - - rate_control->adjusted_fps = MAX(rate_control->fps, new_adjusted_fps); - spice_debug("new-adjusted-fps=%.2f", rate_control->adjusted_fps); - } - rate_control->adjusted_fps_start_time = now; - rate_control->adjusted_fps_num_frames = 0; - } -} - -/* - * dest must be either NULL or allocated by malloc, since it might be freed - * during the encoding, if its size is too small. - * - * return: - * MJPEG_ENCODER_FRAME_UNSUPPORTED : frame cannot be encoded - * MJPEG_ENCODER_FRAME_DROP : frame should be dropped. This value can only be returned - * if mjpeg rate control is active. - * MJPEG_ENCODER_FRAME_ENCODE_DONE : frame encoding started. Continue with - * mjpeg_encoder_encode_scanline. - */ -static int mjpeg_encoder_start_frame(MJpegEncoder *encoder, - SpiceBitmapFmt format, - int width, int height, - uint8_t **dest, size_t *dest_len, - uint32_t frame_mm_time) -{ - uint32_t quality; - - if (rate_control_is_active(encoder)) { - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - uint64_t now; - uint64_t interval; - - now = red_get_monotonic_time(); - - if (!rate_control->adjusted_fps_start_time) { - rate_control->adjusted_fps_start_time = now; - } - mjpeg_encoder_adjust_fps(encoder, now); - interval = (now - rate_control->bit_rate_info.last_frame_time); - - if (interval < (1000*1000*1000) / rate_control->adjusted_fps) { - return MJPEG_ENCODER_FRAME_DROP; - } - - mjpeg_encoder_adjust_params_to_bit_rate(encoder); - - if (!rate_control->during_quality_eval || - rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) { - MJpegEncoderBitRateInfo *bit_rate_info; - - bit_rate_info = &encoder->rate_control.bit_rate_info; - - if (!bit_rate_info->change_start_time) { - bit_rate_info->change_start_time = now; - bit_rate_info->change_start_mm_time = frame_mm_time; - } - bit_rate_info->last_frame_time = now; - } - } - - encoder->cinfo.in_color_space = JCS_RGB; - encoder->cinfo.input_components = 3; - encoder->pixel_converter = NULL; - - switch (format) { - case SPICE_BITMAP_FMT_32BIT: - case SPICE_BITMAP_FMT_RGBA: - encoder->bytes_per_pixel = 4; -#ifdef JCS_EXTENSIONS - encoder->cinfo.in_color_space = JCS_EXT_BGRX; - encoder->cinfo.input_components = 4; -#else - encoder->pixel_converter = pixel_rgb32bpp_to_24; -#endif - break; - case SPICE_BITMAP_FMT_16BIT: - encoder->bytes_per_pixel = 2; - encoder->pixel_converter = pixel_rgb16bpp_to_24; - break; - case SPICE_BITMAP_FMT_24BIT: - encoder->bytes_per_pixel = 3; -#ifdef JCS_EXTENSIONS - encoder->cinfo.in_color_space = JCS_EXT_BGR; -#else - encoder->pixel_converter = pixel_rgb24bpp_to_24; -#endif - break; - default: - spice_debug("unsupported format %d", format); - return MJPEG_ENCODER_FRAME_UNSUPPORTED; - } - - if (encoder->pixel_converter != NULL) { - unsigned int stride = width * 3; - /* check for integer overflow */ - if (stride < width) { - return MJPEG_ENCODER_FRAME_UNSUPPORTED; - } - if (encoder->row_size < stride) { - encoder->row = spice_realloc(encoder->row, stride); - encoder->row_size = stride; - } - } - - spice_jpeg_mem_dest(&encoder->cinfo, dest, dest_len); - - encoder->cinfo.image_width = width; - encoder->cinfo.image_height = height; - jpeg_set_defaults(&encoder->cinfo); - encoder->cinfo.dct_method = JDCT_IFAST; - quality = mjpeg_quality_samples[encoder->rate_control.quality_id]; - jpeg_set_quality(&encoder->cinfo, quality, TRUE); - jpeg_start_compress(&encoder->cinfo, encoder->first_frame); - - encoder->num_frames++; - encoder->avg_quality += quality; - return MJPEG_ENCODER_FRAME_ENCODE_DONE; -} - -static int mjpeg_encoder_encode_scanline(MJpegEncoder *encoder, - uint8_t *src_pixels, - size_t image_width) -{ - unsigned int scanlines_written; - uint8_t *row; - - row = encoder->row; - if (encoder->pixel_converter) { - unsigned int x; - for (x = 0; x < image_width; x++) { - /* src_pixels is expected to be 4 bytes aligned */ - encoder->pixel_converter(src_pixels, row); - row += 3; - src_pixels += encoder->bytes_per_pixel; - } - scanlines_written = jpeg_write_scanlines(&encoder->cinfo, &encoder->row, 1); - } else { - scanlines_written = jpeg_write_scanlines(&encoder->cinfo, &src_pixels, 1); - } - if (scanlines_written == 0) { /* Not enough space */ - jpeg_abort_compress(&encoder->cinfo); - encoder->rate_control.last_enc_size = 0; - return 0; - } - - return scanlines_written; -} - -static size_t mjpeg_encoder_end_frame(MJpegEncoder *encoder) -{ - mem_destination_mgr *dest = (mem_destination_mgr *) encoder->cinfo.dest; - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - - jpeg_finish_compress(&encoder->cinfo); - - encoder->first_frame = FALSE; - rate_control->last_enc_size = dest->pub.next_output_byte - dest->buffer; - rate_control->server_state.num_frames_encoded++; - - if (!rate_control->during_quality_eval || - rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) { - - if (!rate_control->during_quality_eval) { - if (rate_control->num_recent_enc_frames >= MJPEG_AVERAGE_SIZE_WINDOW) { - rate_control->num_recent_enc_frames = 0; - rate_control->sum_recent_enc_size = 0; - } - rate_control->sum_recent_enc_size += rate_control->last_enc_size; - rate_control->num_recent_enc_frames++; - rate_control->adjusted_fps_num_frames++; - } - rate_control->bit_rate_info.sum_enc_size += encoder->rate_control.last_enc_size; - rate_control->bit_rate_info.num_enc_frames++; - } - return encoder->rate_control.last_enc_size; -} - -static inline uint8_t *get_image_line(SpiceChunks *chunks, size_t *offset, - int *chunk_nr, int stride) -{ - uint8_t *ret; - SpiceChunk *chunk; - - chunk = &chunks->chunk[*chunk_nr]; - - if (*offset == chunk->len) { - if (*chunk_nr == chunks->num_chunks - 1) { - return NULL; /* Last chunk */ - } - *offset = 0; - (*chunk_nr)++; - chunk = &chunks->chunk[*chunk_nr]; - } - - if (chunk->len - *offset < stride) { - spice_warning("bad chunk alignment"); - return NULL; - } - ret = chunk->data + *offset; - *offset += stride; - return ret; -} - -static int encode_frame(MJpegEncoder *encoder, const SpiceRect *src, - const SpiceBitmap *image, int top_down) -{ - SpiceChunks *chunks; - uint32_t image_stride; - size_t offset; - int i, chunk; - - chunks = image->data; - offset = 0; - chunk = 0; - image_stride = image->stride; - - const int skip_lines = top_down ? src->top : image->y - (src->bottom - 0); - for (i = 0; i < skip_lines; i++) { - get_image_line(chunks, &offset, &chunk, image_stride); - } - - const unsigned int stream_height = src->bottom - src->top; - const unsigned int stream_width = src->right - src->left; - - for (i = 0; i < stream_height; i++) { - uint8_t *src_line = get_image_line(chunks, &offset, &chunk, image_stride); - - if (!src_line) { - return FALSE; - } - - src_line += src->left * mjpeg_encoder_get_bytes_per_pixel(encoder); - if (mjpeg_encoder_encode_scanline(encoder, src_line, stream_width) == 0) { - return FALSE; - } - } - - return TRUE; -} - -int mjpeg_encoder_encode_frame(MJpegEncoder *encoder, - const SpiceBitmap *bitmap, int width, int height, - const SpiceRect *src, - int top_down, uint32_t frame_mm_time, - uint8_t **outbuf, size_t *outbuf_size, - int *data_size) -{ - int ret = mjpeg_encoder_start_frame(encoder, bitmap->format, - width, height, outbuf, outbuf_size, - frame_mm_time); - if (ret != MJPEG_ENCODER_FRAME_ENCODE_DONE) { - return ret; - } - - if (!encode_frame(encoder, src, bitmap, top_down)) { - return MJPEG_ENCODER_FRAME_UNSUPPORTED; - } - - *data_size = mjpeg_encoder_end_frame(encoder); - - return MJPEG_ENCODER_FRAME_ENCODE_DONE; -} - - -static void mjpeg_encoder_quality_eval_stop(MJpegEncoder *encoder) -{ - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - uint32_t quality_id; - uint32_t fps; - - if (!rate_control->during_quality_eval) { - return; - } - switch (rate_control->quality_eval_data.type) { - case MJPEG_QUALITY_EVAL_TYPE_UPGRADE: - quality_id = rate_control->quality_eval_data.min_quality_id; - fps = rate_control->quality_eval_data.min_quality_fps; - break; - case MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE: - quality_id = rate_control->quality_eval_data.max_quality_id; - fps = rate_control->quality_eval_data.max_quality_fps; - break; - case MJPEG_QUALITY_EVAL_TYPE_SET: - quality_id = MJPEG_QUALITY_SAMPLE_NUM / 2; - fps = MJPEG_MAX_FPS / 2; - break; - default: - spice_warning("unexected"); - return; - } - mjpeg_encoder_reset_quality(encoder, quality_id, fps, 0); - spice_debug("during quality evaluation: canceling." - "reset quality to %d fps %d", - mjpeg_quality_samples[rate_control->quality_id], rate_control->fps); -} - -static void mjpeg_encoder_decrease_bit_rate(MJpegEncoder *encoder) -{ - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info; - uint64_t measured_byte_rate; - uint32_t measured_fps; - uint64_t decrease_size; - - mjpeg_encoder_quality_eval_stop(encoder); - - rate_control->client_state.max_video_latency = 0; - rate_control->client_state.max_audio_latency = 0; - if (rate_control->warmup_start_time) { - uint64_t now; - - now = red_get_monotonic_time(); - if (now - rate_control->warmup_start_time < MJPEG_WARMUP_TIME*1000*1000) { - spice_debug("during warmup. ignoring"); - return; - } else { - rate_control->warmup_start_time = 0; - } - } - - if (bit_rate_info->num_enc_frames > MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES || - bit_rate_info->num_enc_frames > rate_control->fps) { - double duration_sec; - - duration_sec = (bit_rate_info->last_frame_time - bit_rate_info->change_start_time); - duration_sec /= (1000.0 * 1000.0 * 1000.0); - measured_byte_rate = bit_rate_info->sum_enc_size / duration_sec; - measured_fps = bit_rate_info->num_enc_frames / duration_sec; - decrease_size = bit_rate_info->sum_enc_size / bit_rate_info->num_enc_frames; - spice_debug("bit rate esitimation %.2f (Mbps) fps %u", - measured_byte_rate*8/1024.0/1024, - measured_fps); - } else { - measured_byte_rate = rate_control->byte_rate; - measured_fps = rate_control->fps; - decrease_size = measured_byte_rate/measured_fps; - spice_debug("bit rate not re-estimated %.2f (Mbps) fps %u", - measured_byte_rate*8/1024.0/1024, - measured_fps); - } - - measured_byte_rate = MIN(rate_control->byte_rate, measured_byte_rate); - - if (decrease_size >= measured_byte_rate) { - decrease_size = measured_byte_rate / 2; - } - - rate_control->byte_rate = measured_byte_rate - decrease_size; - bit_rate_info->change_start_time = 0; - bit_rate_info->change_start_mm_time = 0; - bit_rate_info->last_frame_time = 0; - bit_rate_info->num_enc_frames = 0; - bit_rate_info->sum_enc_size = 0; - bit_rate_info->was_upgraded = FALSE; - - spice_debug("decrease bit rate %.2f (Mbps)", rate_control->byte_rate * 8 / 1024.0/1024.0); - mjpeg_encoder_quality_eval_set_downgrade(encoder, - MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE, - rate_control->quality_id, - rate_control->fps); -} - -static void mjpeg_encoder_handle_negative_client_stream_report(MJpegEncoder *encoder, - uint32_t report_end_frame_mm_time) -{ - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - - spice_debug(NULL); - - if ((rate_control->bit_rate_info.change_start_mm_time > report_end_frame_mm_time || - !rate_control->bit_rate_info.change_start_mm_time) && - !rate_control->bit_rate_info.was_upgraded) { - spice_debug("ignoring, a downgrade has already occurred later to the report time"); - return; - } - - mjpeg_encoder_decrease_bit_rate(encoder); -} - -static void mjpeg_encoder_increase_bit_rate(MJpegEncoder *encoder) -{ - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info; - uint64_t measured_byte_rate; - uint32_t measured_fps; - uint64_t increase_size; - - - if (bit_rate_info->num_enc_frames > MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES || - bit_rate_info->num_enc_frames > rate_control->fps) { - uint64_t avg_frame_size; - double duration_sec; - - duration_sec = (bit_rate_info->last_frame_time - bit_rate_info->change_start_time); - duration_sec /= (1000.0 * 1000.0 * 1000.0); - measured_byte_rate = bit_rate_info->sum_enc_size / duration_sec; - measured_fps = bit_rate_info->num_enc_frames / duration_sec; - avg_frame_size = bit_rate_info->sum_enc_size / bit_rate_info->num_enc_frames; - spice_debug("bit rate esitimation %.2f (Mbps) defined %.2f" - " fps %u avg-frame-size=%.2f (KB)", - measured_byte_rate*8/1024.0/1024, - rate_control->byte_rate*8/1024.0/1024, - measured_fps, - avg_frame_size/1024.0); - increase_size = avg_frame_size; - } else { - spice_debug("not enough samples for measuring the bit rate. no change"); - return; - } - - - mjpeg_encoder_quality_eval_stop(encoder); - - if (measured_byte_rate + increase_size < rate_control->byte_rate) { - spice_debug("measured byte rate is small: not upgrading, just re-evaluating"); - } else { - rate_control->byte_rate = MIN(measured_byte_rate, rate_control->byte_rate) + increase_size; - } - - bit_rate_info->change_start_time = 0; - bit_rate_info->change_start_mm_time = 0; - bit_rate_info->last_frame_time = 0; - bit_rate_info->num_enc_frames = 0; - bit_rate_info->sum_enc_size = 0; - bit_rate_info->was_upgraded = TRUE; - - spice_debug("increase bit rate %.2f (Mbps)", rate_control->byte_rate * 8 / 1024.0/1024.0); - mjpeg_encoder_quality_eval_set_upgrade(encoder, - MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE, - rate_control->quality_id, - rate_control->fps); -} - -static void mjpeg_encoder_handle_positive_client_stream_report(MJpegEncoder *encoder, - uint32_t report_start_frame_mm_time) -{ - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - MJpegEncoderBitRateInfo *bit_rate_info = &rate_control->bit_rate_info; - int stable_client_mm_time; - int timeout; - - if (rate_control->during_quality_eval && - rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) { - spice_debug("during quality evaluation (rate change). ignoring report"); - return; - } - - if ((rate_control->fps > MJPEG_IMPROVE_QUALITY_FPS_STRICT_TH || - rate_control->fps >= mjpeg_encoder_get_source_fps(encoder)) && - rate_control->quality_id > MJPEG_QUALITY_SAMPLE_NUM / 2) { - timeout = MJPEG_CLIENT_POSITIVE_REPORT_STRICT_TIMEOUT; - } else { - timeout = MJPEG_CLIENT_POSITIVE_REPORT_TIMEOUT; - } - - stable_client_mm_time = (int)report_start_frame_mm_time - bit_rate_info->change_start_mm_time; - - if (!bit_rate_info->change_start_mm_time || stable_client_mm_time < timeout) { - /* assessing the stability of the current setting and only then - * respond to the report */ - spice_debug("no drops, but not enough time has passed for assessing" - "the playback stability since the last bit rate change"); - return; - } - mjpeg_encoder_increase_bit_rate(encoder); -} - -/* - * the video playback jitter buffer should be at least (send_time*2 + net_latency) for - * preventing underflow - */ -static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size, - uint64_t byte_rate, - uint32_t latency) -{ - uint32_t one_frame_time; - uint32_t min_delay; - - if (!frame_enc_size || !byte_rate) { - return latency; - } - one_frame_time = (frame_enc_size*1000)/byte_rate; - - min_delay = MIN(one_frame_time*2 + latency, MJPEG_MAX_CLIENT_PLAYBACK_DELAY); - return min_delay; -} - -#define MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR 0.5 -#define MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR 1.25 -#define MJPEG_VIDEO_DELAY_TH -15 - -void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder, - uint32_t num_frames, - uint32_t num_drops, - uint32_t start_frame_mm_time, - uint32_t end_frame_mm_time, - int32_t end_frame_delay, - uint32_t audio_delay) -{ - MJpegEncoderRateControl *rate_control = &encoder->rate_control; - MJpegEncoderClientState *client_state = &rate_control->client_state; - uint64_t avg_enc_size = 0; - uint32_t min_playback_delay; - int is_video_delay_small = FALSE; - - spice_debug("client report: #frames %u, #drops %d, duration %u video-delay %d audio-delay %u", - num_frames, num_drops, - end_frame_mm_time - start_frame_mm_time, - end_frame_delay, audio_delay); - - if (!rate_control_is_active(encoder)) { - spice_debug("rate control was not activated: ignoring"); - return; - } - if (rate_control->during_quality_eval) { - if (rate_control->quality_eval_data.type == MJPEG_QUALITY_EVAL_TYPE_DOWNGRADE && - rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) { - spice_debug("during rate downgrade evaluation"); - return; - } - } - - if (rate_control->num_recent_enc_frames) { - avg_enc_size = rate_control->sum_recent_enc_size / - rate_control->num_recent_enc_frames; - } - spice_debug("recent size avg %.2f (KB)", avg_enc_size / 1024.0); - min_playback_delay = get_min_required_playback_delay(avg_enc_size, rate_control->byte_rate, - mjpeg_encoder_get_latency(encoder)); - spice_debug("min-delay %u client-delay %d", min_playback_delay, end_frame_delay); - - if (min_playback_delay > end_frame_delay) { - uint32_t src_fps = mjpeg_encoder_get_source_fps(encoder); - /* - * if the stream is at its highest rate, we can't estimate the "real" - * network bit rate and the min_playback_delay - */ - if (rate_control->quality_id != MJPEG_QUALITY_SAMPLE_NUM - 1 || - rate_control->fps < MIN(src_fps, MJPEG_MAX_FPS) || end_frame_delay < 0) { - is_video_delay_small = TRUE; - if (encoder->cbs.update_client_playback_delay) { - encoder->cbs.update_client_playback_delay(encoder->cbs_opaque, - min_playback_delay); - } - } - } - - - /* - * If the audio latency has decreased (since the start of the current - * sequence of positive reports), and the video latency is bigger, slow down - * the video rate - */ - if (end_frame_delay > 0 && - audio_delay < MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR*client_state->max_audio_latency && - end_frame_delay > MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR*audio_delay) { - spice_debug("video_latency >> audio_latency && audio_latency << max (%u)", - client_state->max_audio_latency); - mjpeg_encoder_handle_negative_client_stream_report(encoder, - end_frame_mm_time); - return; - } - - if (end_frame_delay < MJPEG_VIDEO_DELAY_TH) { - mjpeg_encoder_handle_negative_client_stream_report(encoder, - end_frame_mm_time); - } else { - double major_delay_decrease_thresh; - double medium_delay_decrease_thresh; - - client_state->max_video_latency = MAX(end_frame_delay, client_state->max_video_latency); - client_state->max_audio_latency = MAX(audio_delay, client_state->max_audio_latency); - - medium_delay_decrease_thresh = client_state->max_video_latency; - medium_delay_decrease_thresh *= MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR; - - major_delay_decrease_thresh = medium_delay_decrease_thresh; - major_delay_decrease_thresh *= MJPEG_PLAYBACK_LATENCY_DECREASE_FACTOR; - /* - * since the bit rate and the required latency are only evaluation based on the - * reports we got till now, we assume that the latency is too low only if it - * was higher during the time that passed since the last report that resulted - * in a bit rate decrement. If we find that the latency has decreased, it might - * suggest that the stream bit rate is too high. - */ - if ((end_frame_delay < medium_delay_decrease_thresh && - is_video_delay_small) || end_frame_delay < major_delay_decrease_thresh) { - spice_debug("downgrade due to short video delay (last=%u, past-max=%u", - end_frame_delay, client_state->max_video_latency); - mjpeg_encoder_handle_negative_client_stream_report(encoder, - end_frame_mm_time); - } else if (!num_drops) { - mjpeg_encoder_handle_positive_client_stream_report(encoder, - start_frame_mm_time); - - } - } -} - -void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder) -{ - encoder->rate_control.server_state.num_frames_dropped++; - mjpeg_encoder_process_server_drops(encoder); -} - -/* - * decrease the bit rate if the drop rate on the sever side exceeds a pre defined - * threshold. - */ -static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder) -{ - MJpegEncoderServerState *server_state = &encoder->rate_control.server_state; - uint32_t num_frames_total; - double drop_factor; - uint32_t fps; - - fps = MIN(encoder->rate_control.fps, mjpeg_encoder_get_source_fps(encoder)); - if (server_state->num_frames_encoded < fps * MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL) { - return; - } - - num_frames_total = server_state->num_frames_dropped + server_state->num_frames_encoded; - drop_factor = ((double)server_state->num_frames_dropped) / num_frames_total; - - spice_debug("#drops %u total %u fps %u src-fps %u", - server_state->num_frames_dropped, - num_frames_total, - encoder->rate_control.fps, - mjpeg_encoder_get_source_fps(encoder)); - - if (drop_factor > MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH) { - mjpeg_encoder_decrease_bit_rate(encoder); - } - server_state->num_frames_encoded = 0; - server_state->num_frames_dropped = 0; -} - -uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder) -{ - return encoder->rate_control.byte_rate * 8; -} - -void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats) -{ - spice_assert(encoder != NULL && stats != NULL); - stats->starting_bit_rate = encoder->starting_bit_rate; - stats->cur_bit_rate = mjpeg_encoder_get_bit_rate(encoder); - stats->avg_quality = (double)encoder->avg_quality / encoder->num_frames; -} - -MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate, - MJpegEncoderRateControlCbs *cbs, - void *cbs_opaque) -{ - MJpegEncoder *encoder = spice_new0(MJpegEncoder, 1); - - encoder->first_frame = TRUE; - encoder->rate_control.byte_rate = starting_bit_rate / 8; - encoder->starting_bit_rate = starting_bit_rate; - - if (cbs) { - struct timespec time; - - clock_gettime(CLOCK_MONOTONIC, &time); - encoder->cbs = *cbs; - encoder->cbs_opaque = cbs_opaque; - mjpeg_encoder_reset_quality(encoder, MJPEG_QUALITY_SAMPLE_NUM / 2, 5, 0); - encoder->rate_control.during_quality_eval = TRUE; - encoder->rate_control.quality_eval_data.type = MJPEG_QUALITY_EVAL_TYPE_SET; - encoder->rate_control.quality_eval_data.reason = MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE; - encoder->rate_control.warmup_start_time = ((uint64_t) time.tv_sec) * 1000000000 + time.tv_nsec; - } else { - encoder->cbs.get_roundtrip_ms = NULL; - mjpeg_encoder_reset_quality(encoder, MJPEG_LEGACY_STATIC_QUALITY_ID, MJPEG_MAX_FPS, 0); - } - - encoder->cinfo.err = jpeg_std_error(&encoder->jerr); - jpeg_create_compress(&encoder->cinfo); - - return encoder; -} diff --git a/server/mjpeg_encoder.h b/server/mjpeg_encoder.h deleted file mode 100644 index d070e70..0000000 --- a/server/mjpeg_encoder.h +++ /dev/null @@ -1,100 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef _H_MJPEG_ENCODER -#define _H_MJPEG_ENCODER - -#include "red_common.h" - -enum { - MJPEG_ENCODER_FRAME_UNSUPPORTED = -1, - MJPEG_ENCODER_FRAME_DROP, - MJPEG_ENCODER_FRAME_ENCODE_DONE, -}; - -typedef struct MJpegEncoder MJpegEncoder; - -/* - * Callbacks required for controling and adjusting - * the stream bit rate: - * get_roundtrip_ms: roundtrip time in milliseconds - * get_source_fps: the input frame rate (#frames per second), i.e., - * the rate of frames arriving from the guest to spice-server, - * before any drops. - */ -typedef struct MJpegEncoderRateControlCbs { - uint32_t (*get_roundtrip_ms)(void *opaque); - uint32_t (*get_source_fps)(void *opaque); - void (*update_client_playback_delay)(void *opaque, uint32_t delay_ms); -} MJpegEncoderRateControlCbs; - -typedef struct MJpegEncoderStats { - uint64_t starting_bit_rate; - uint64_t cur_bit_rate; - double avg_quality; -} MJpegEncoderStats; - -MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate, - MJpegEncoderRateControlCbs *cbs, void *opaque); -void mjpeg_encoder_destroy(MJpegEncoder *encoder); - -int mjpeg_encoder_encode_frame(MJpegEncoder *encoder, - const SpiceBitmap *bitmap, int width, int height, - const SpiceRect *src, - int top_down, uint32_t frame_mm_time, - uint8_t **outbuf, size_t *outbuf_size, - int *data_size); - -/* - * bit rate control - */ - -/* - * Data that should be periodically obtained from the client. The report contains: - * num_frames : the number of frames that reached the client during the time - * the report is referring to. - * num_drops : the part of the above frames that was dropped by the client due to - * late arrival time. - * start_frame_mm_time: the mm_time of the first frame included in the report - * end_frame_mm_time : the mm_time of the last_frame included in the report - * end_frame_delay : (end_frame_mm_time - client_mm_time) - * audio delay : the latency of the audio playback. - * If there is no audio playback, set it to MAX_UINT. - * - */ -void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder, - uint32_t num_frames, - uint32_t num_drops, - uint32_t start_frame_mm_time, - uint32_t end_frame_mm_time, - int32_t end_frame_delay, - uint32_t audio_delay); - -/* - * Notify the encoder each time a frame is dropped due to pipe - * congestion. - * We can deduce the client state by the frame dropping rate in the server. - * Monitoring the frame drops can help in fine tuning the playback parameters - * when the client reports are delayed. - */ -void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder); - -uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder); -void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats); - -#endif diff --git a/server/red_channel.c b/server/red_channel.c index 609c83f..948d354 100644 --- a/server/red_channel.c +++ b/server/red_channel.c @@ -42,7 +42,7 @@ #include "red_channel.h" #include "reds.h" #include "reds_stream.h" -#include "main_dispatcher.h" +#include "main-dispatcher.h" #include "utils.h" typedef struct EmptyMsgPipeItem { diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c index a7825f5..b8b77d7 100644 --- a/server/red_dispatcher.c +++ b/server/red_dispatcher.c @@ -32,7 +32,7 @@ #include "spice.h" #include "red_worker.h" -#include "reds_sw_canvas.h" +#include "sw-canvas.h" #include "reds.h" #include "dispatcher.h" #include "red_parse_qxl.h" diff --git a/server/red_memslots.c b/server/red_memslots.c deleted file mode 100644 index 0d2d963..0000000 --- a/server/red_memslots.c +++ /dev/null @@ -1,184 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009,2010 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <inttypes.h> - -#include "red_common.h" -#include "red_memslots.h" - -static unsigned long __get_clean_virt(RedMemSlotInfo *info, QXLPHYSICAL addr) -{ - return addr & info->memslot_clean_virt_mask; -} - -static void print_memslots(RedMemSlotInfo *info) -{ - int i; - int x; - - for (i = 0; i < info->num_memslots_groups; ++i) { - for (x = 0; x < info->num_memslots; ++x) { - if (!info->mem_slots[i][x].virt_start_addr && - !info->mem_slots[i][x].virt_end_addr) { - continue; - } - printf("id %d, group %d, virt start %lx, virt end %lx, generation %u, delta %lx\n", - x, i, info->mem_slots[i][x].virt_start_addr, - info->mem_slots[i][x].virt_end_addr, info->mem_slots[i][x].generation, - info->mem_slots[i][x].address_delta); - } - } -} - -/* return 1 if validation successfull, 0 otherwise */ -int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id, - uint32_t add_size, uint32_t group_id) -{ - MemSlot *slot; - - slot = &info->mem_slots[group_id][slot_id]; - if ((virt + add_size) < virt) { - spice_critical("virtual address overlap"); - return 0; - } - - if (virt < slot->virt_start_addr || (virt + add_size) > slot->virt_end_addr) { - print_memslots(info); - spice_critical("virtual address out of range\n" - " virt=0x%lx+0x%x slot_id=%d group_id=%d\n" - " slot=0x%lx-0x%lx delta=0x%lx", - virt, add_size, slot_id, group_id, - slot->virt_start_addr, slot->virt_end_addr, slot->address_delta); - return 0; - } - return 1; -} - -/* - * return virtual address if successful, which may be 0. - * returns 0 and sets error to 1 if an error condition occurs. - */ -unsigned long memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size, - int group_id, int *error) -{ - int slot_id; - int generation; - unsigned long h_virt; - - MemSlot *slot; - - *error = 0; - if (group_id > info->num_memslots_groups) { - spice_critical("group_id too big"); - *error = 1; - return 0; - } - - slot_id = memslot_get_id(info, addr); - if (slot_id > info->num_memslots) { - print_memslots(info); - spice_critical("slot_id %d too big, addr=%" PRIx64, slot_id, addr); - *error = 1; - return 0; - } - - slot = &info->mem_slots[group_id][slot_id]; - - generation = memslot_get_generation(info, addr); - if (generation != slot->generation) { - print_memslots(info); - spice_critical("address generation is not valid, group_id %d, slot_id %d, gen %d, slot_gen %d\n", - group_id, slot_id, generation, slot->generation); - *error = 1; - return 0; - } - - h_virt = __get_clean_virt(info, addr); - h_virt += slot->address_delta; - - if (!memslot_validate_virt(info, h_virt, slot_id, add_size, group_id)) { - *error = 1; - return 0; - } - - return h_virt; -} - -void memslot_info_init(RedMemSlotInfo *info, - uint32_t num_groups, uint32_t num_slots, - uint8_t generation_bits, - uint8_t id_bits, - uint8_t internal_groupslot_id) -{ - uint32_t i; - - spice_return_if_fail(num_slots > 0); - spice_return_if_fail(num_groups > 0); - - info->num_memslots_groups = num_groups; - info->num_memslots = num_slots; - info->generation_bits = generation_bits; - info->mem_slot_bits = id_bits; - info->internal_groupslot_id = internal_groupslot_id; - - info->mem_slots = spice_new(MemSlot *, num_groups); - - for (i = 0; i < num_groups; ++i) { - info->mem_slots[i] = spice_new0(MemSlot, num_slots); - } - - /* TODO: use QXLPHYSICAL_BITS */ - info->memslot_id_shift = 64 - info->mem_slot_bits; - info->memslot_gen_shift = 64 - (info->mem_slot_bits + info->generation_bits); - info->memslot_gen_mask = ~((QXLPHYSICAL)-1 << info->generation_bits); - info->memslot_clean_virt_mask = (((QXLPHYSICAL)(-1)) >> - (info->mem_slot_bits + info->generation_bits)); -} - -void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id, - uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end, - uint32_t generation) -{ - spice_return_if_fail(info->num_memslots_groups > slot_group_id); - spice_return_if_fail(info->num_memslots > slot_id); - - info->mem_slots[slot_group_id][slot_id].address_delta = addr_delta; - info->mem_slots[slot_group_id][slot_id].virt_start_addr = virt_start; - info->mem_slots[slot_group_id][slot_id].virt_end_addr = virt_end; - info->mem_slots[slot_group_id][slot_id].generation = generation; -} - -void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id) -{ - spice_return_if_fail(info->num_memslots_groups > slot_group_id); - spice_return_if_fail(info->num_memslots > slot_id); - - info->mem_slots[slot_group_id][slot_id].virt_start_addr = 0; - info->mem_slots[slot_group_id][slot_id].virt_end_addr = 0; -} - -void memslot_info_reset(RedMemSlotInfo *info) -{ - uint32_t i; - for (i = 0; i < info->num_memslots_groups; ++i) { - memset(info->mem_slots[i], 0, sizeof(MemSlot) * info->num_memslots); - } -} diff --git a/server/red_memslots.h b/server/red_memslots.h deleted file mode 100644 index a40050c..0000000 --- a/server/red_memslots.h +++ /dev/null @@ -1,72 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009,2010 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef _H_REDMEMSLOTS -#define _H_REDMEMSLOTS - -#include "red_common.h" - -#include <spice/qxl_dev.h> - -typedef struct MemSlot { - int generation; - unsigned long virt_start_addr; - unsigned long virt_end_addr; - long address_delta; -} MemSlot; - -typedef struct RedMemSlotInfo { - MemSlot **mem_slots; - uint32_t num_memslots_groups; - uint32_t num_memslots; - uint8_t mem_slot_bits; - uint8_t generation_bits; - uint8_t memslot_id_shift; - uint8_t memslot_gen_shift; - uint8_t internal_groupslot_id; - unsigned long memslot_gen_mask; - unsigned long memslot_clean_virt_mask; -} RedMemSlotInfo; - -static inline int memslot_get_id(RedMemSlotInfo *info, uint64_t addr) -{ - return addr >> info->memslot_id_shift; -} - -static inline int memslot_get_generation(RedMemSlotInfo *info, uint64_t addr) -{ - return (addr >> info->memslot_gen_shift) & info->memslot_gen_mask; -} - -int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id, - uint32_t add_size, uint32_t group_id); -unsigned long memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size, - int group_id, int *error); - -void memslot_info_init(RedMemSlotInfo *info, - uint32_t num_groups, uint32_t num_slots, - uint8_t generation_bits, - uint8_t id_bits, - uint8_t internal_groupslot_id); -void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id, - uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end, - uint32_t generation); -void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id); -void memslot_info_reset(RedMemSlotInfo *info); - -#endif diff --git a/server/red_parse_qxl.c b/server/red_parse_qxl.c index 6e2ca99..522a915 100644 --- a/server/red_parse_qxl.c +++ b/server/red_parse_qxl.c @@ -25,7 +25,7 @@ #include "common/lz_common.h" #include "spice-bitmap-utils.h" #include "red_common.h" -#include "red_memslots.h" +#include "memslot.h" #include "red_parse_qxl.h" /* Max size in bytes for any data field used in a QXL command. diff --git a/server/red_parse_qxl.h b/server/red_parse_qxl.h index b3b28e1..09059f5 100644 --- a/server/red_parse_qxl.h +++ b/server/red_parse_qxl.h @@ -21,7 +21,7 @@ #include <spice/qxl_dev.h> #include "red_common.h" -#include "red_memslots.h" +#include "memslot.h" typedef struct RedDrawable { int refs; diff --git a/server/red_record_qxl.c b/server/red_record_qxl.c index 17f17bd..52c0e81 100644 --- a/server/red_record_qxl.c +++ b/server/red_record_qxl.c @@ -23,9 +23,9 @@ #include <inttypes.h> #include "red_worker.h" #include "red_common.h" -#include "red_memslots.h" +#include "memslot.h" #include "red_parse_qxl.h" -#include "zlib_encoder.h" +#include "zlib-encoder.h" #if 0 static void hexdump_qxl(RedMemSlotInfo *slots, int group_id, diff --git a/server/red_record_qxl.h b/server/red_record_qxl.h index b737db8..6fcbec9 100644 --- a/server/red_record_qxl.h +++ b/server/red_record_qxl.h @@ -21,7 +21,7 @@ #include <spice/qxl_dev.h> #include "red_common.h" -#include "red_memslots.h" +#include "memslot.h" void red_record_dev_input_primary_surface_create( FILE *fd, QXLDevSurfaceCreate *surface, uint8_t *line_0); diff --git a/server/red_replay_qxl.c b/server/red_replay_qxl.c index ad1a8fd..6e32588 100644 --- a/server/red_replay_qxl.c +++ b/server/red_replay_qxl.c @@ -26,7 +26,7 @@ #include "reds.h" #include "red_worker.h" #include "red_common.h" -#include "red_memslots.h" +#include "memslot.h" #include "red_parse_qxl.h" #include "red_replay_qxl.h" #include <glib.h> diff --git a/server/reds.c b/server/reds.c index 8b3c3cb..5891034 100644 --- a/server/reds.c +++ b/server/reds.c @@ -56,16 +56,16 @@ #include "spice.h" #include "reds.h" #include "agent-msg-filter.h" -#include "inputs_channel.h" -#include "main_channel.h" +#include "inputs-channel.h" +#include "main-channel.h" #include "red_common.h" #include "red_dispatcher.h" -#include "main_dispatcher.h" -#include "snd_worker.h" +#include "main-dispatcher.h" +#include "sound.h" #include "stat.h" #include "demarshallers.h" -#include "char_device.h" -#include "migration_protocol.h" +#include "char-device.h" +#include "migration-protocol.h" #ifdef USE_SMARTCARD #include "smartcard.h" #endif diff --git a/server/reds.h b/server/reds.h index fcdc5eb..0584694 100644 --- a/server/reds.h +++ b/server/reds.h @@ -28,7 +28,7 @@ #include "common/messages.h" #include "spice.h" #include "red_channel.h" -#include "migration_protocol.h" +#include "migration-protocol.h" struct QXLState { QXLInterface *qif; @@ -75,7 +75,7 @@ extern SpiceImageCompression image_compression; extern spice_wan_compression_t jpeg_state; extern spice_wan_compression_t zlib_glz_state; -// Temporary measures to make splitting reds.c to inputs_channel.c easier +// Temporary measures to make splitting reds.c to inputs-channel.c easier /* should be called only from main_dispatcher */ void reds_client_disconnect(RedClient *client); diff --git a/server/reds_stream.c b/server/reds_stream.c index 3b47391..6f5c43f 100644 --- a/server/reds_stream.c +++ b/server/reds_stream.c @@ -19,7 +19,7 @@ #include <config.h> #endif -#include "main_dispatcher.h" +#include "main-dispatcher.h" #include "red_common.h" #include "reds_stream.h" #include "common/log.h" diff --git a/server/reds_sw_canvas.c b/server/reds_sw_canvas.c deleted file mode 100644 index 297df37..0000000 --- a/server/reds_sw_canvas.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - Copyright (C) 2011 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include "common/spice_common.h" - -#include "reds_sw_canvas.h" -#define SW_CANVAS_IMAGE_CACHE -#include "common/sw_canvas.c" -#undef SW_CANVAS_IMAGE_CACHE diff --git a/server/reds_sw_canvas.h b/server/reds_sw_canvas.h deleted file mode 100644 index 96a4c0c..0000000 --- a/server/reds_sw_canvas.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - Copyright (C) 2011 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifndef _H_REDS_SW_CANVAS -#define _H_REDS_SW_CANVAS - -#define SW_CANVAS_IMAGE_CACHE -#include "common/sw_canvas.h" -#undef SW_CANVAS_IMAGE_CACHE - -#endif diff --git a/server/smartcard.c b/server/smartcard.c index aad22aa..928e27b8 100644 --- a/server/smartcard.c +++ b/server/smartcard.c @@ -23,10 +23,10 @@ #include <vscard_common.h> #include "reds.h" -#include "char_device.h" +#include "char-device.h" #include "red_channel.h" #include "smartcard.h" -#include "migration_protocol.h" +#include "migration-protocol.h" /* * TODO: the code doesn't really support multiple readers. diff --git a/server/snd_worker.c b/server/snd_worker.c deleted file mode 100644 index b039939..0000000 --- a/server/snd_worker.c +++ /dev/null @@ -1,1625 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <fcntl.h> -#include <errno.h> -#include <limits.h> -#include <sys/socket.h> -#include <netinet/ip.h> -#include <netinet/tcp.h> - -#include "common/marshaller.h" -#include "common/generated_server_marshallers.h" - -#include "spice.h" -#include "red_common.h" -#include "main_channel.h" -#include "reds.h" -#include "red_dispatcher.h" -#include "snd_worker.h" -#include "common/snd_codec.h" -#include "demarshallers.h" - -#ifndef IOV_MAX -#define IOV_MAX 1024 -#endif - -#define SND_RECEIVE_BUF_SIZE (16 * 1024 * 2) -#define RECORD_SAMPLES_SIZE (SND_RECEIVE_BUF_SIZE >> 2) - -enum PlaybackCommand { - SND_PLAYBACK_MIGRATE, - SND_PLAYBACK_MODE, - SND_PLAYBACK_CTRL, - SND_PLAYBACK_PCM, - SND_PLAYBACK_VOLUME, - SND_PLAYBACK_LATENCY, -}; - -enum RecordCommand { - SND_RECORD_MIGRATE, - SND_RECORD_CTRL, - SND_RECORD_VOLUME, -}; - -#define SND_PLAYBACK_MIGRATE_MASK (1 << SND_PLAYBACK_MIGRATE) -#define SND_PLAYBACK_MODE_MASK (1 << SND_PLAYBACK_MODE) -#define SND_PLAYBACK_CTRL_MASK (1 << SND_PLAYBACK_CTRL) -#define SND_PLAYBACK_PCM_MASK (1 << SND_PLAYBACK_PCM) -#define SND_PLAYBACK_VOLUME_MASK (1 << SND_PLAYBACK_VOLUME) -#define SND_PLAYBACK_LATENCY_MASK ( 1 << SND_PLAYBACK_LATENCY) - -#define SND_RECORD_MIGRATE_MASK (1 << SND_RECORD_MIGRATE) -#define SND_RECORD_CTRL_MASK (1 << SND_RECORD_CTRL) -#define SND_RECORD_VOLUME_MASK (1 << SND_RECORD_VOLUME) - -typedef struct SndChannel SndChannel; -typedef void (*snd_channel_send_messages_proc)(void *in_channel); -typedef int (*snd_channel_handle_message_proc)(SndChannel *channel, size_t size, uint32_t type, void *message); -typedef void (*snd_channel_on_message_done_proc)(SndChannel *channel); -typedef void (*snd_channel_cleanup_channel_proc)(SndChannel *channel); - -typedef struct SndWorker SndWorker; - -struct SndChannel { - RedsStream *stream; - SndWorker *worker; - spice_parse_channel_func_t parser; - int refs; - - RedChannelClient *channel_client; - - int active; - int client_active; - int blocked; - - uint32_t command; - uint32_t ack_generation; - uint32_t client_ack_generation; - uint32_t out_messages; - uint32_t ack_messages; - - struct { - uint64_t serial; - SpiceMarshaller *marshaller; - uint32_t size; - uint32_t pos; - } send_data; - - struct { - uint8_t buf[SND_RECEIVE_BUF_SIZE]; - uint8_t *message_start; - uint8_t *now; - uint8_t *end; - } receive_data; - - snd_channel_send_messages_proc send_messages; - snd_channel_handle_message_proc handle_message; - snd_channel_on_message_done_proc on_message_done; - snd_channel_cleanup_channel_proc cleanup; -}; - -typedef struct PlaybackChannel PlaybackChannel; - -typedef struct AudioFrame AudioFrame; -struct AudioFrame { - uint32_t time; - uint32_t samples[SND_CODEC_MAX_FRAME_SIZE]; - PlaybackChannel *channel; - AudioFrame *next; -}; - -struct PlaybackChannel { - SndChannel base; - AudioFrame frames[3]; - AudioFrame *free_frames; - AudioFrame *in_progress; - AudioFrame *pending_frame; - uint32_t mode; - uint32_t latency; - SndCodec codec; - uint8_t encode_buf[SND_CODEC_MAX_COMPRESSED_BYTES]; -}; - -struct SndWorker { - RedChannel *base_channel; - SndChannel *connection; - SndWorker *next; - int active; -}; - -typedef struct SpiceVolumeState { - uint8_t volume_nchannels; - uint16_t *volume; - int mute; -} SpiceVolumeState; - -struct SpicePlaybackState { - struct SndWorker worker; - SpicePlaybackInstance *sin; - SpiceVolumeState volume; - uint32_t frequency; -}; - -struct SpiceRecordState { - struct SndWorker worker; - SpiceRecordInstance *sin; - SpiceVolumeState volume; - uint32_t frequency; -}; - -typedef struct RecordChannel { - SndChannel base; - uint32_t samples[RECORD_SAMPLES_SIZE]; - uint32_t write_pos; - uint32_t read_pos; - uint32_t mode; - uint32_t mode_time; - uint32_t start_time; - SndCodec codec; - uint8_t decode_buf[SND_CODEC_MAX_FRAME_BYTES]; -} RecordChannel; - -static SndWorker *workers; -static uint32_t playback_compression = TRUE; - -static void snd_receive(void* data); - -static SndChannel *snd_channel_get(SndChannel *channel) -{ - channel->refs++; - return channel; -} - -static SndChannel *snd_channel_put(SndChannel *channel) -{ - if (!--channel->refs) { - spice_printerr("SndChannel=%p freed", channel); - free(channel); - return NULL; - } - return channel; -} - -static void snd_disconnect_channel(SndChannel *channel) -{ - SndWorker *worker; - - if (!channel || !channel->stream) { - spice_debug("not connected"); - return; - } - spice_debug("SndChannel=%p rcc=%p type=%d", - channel, channel->channel_client, channel->channel_client->channel->type); - worker = channel->worker; - channel->cleanup(channel); - red_channel_client_disconnect(worker->connection->channel_client); - worker->connection->channel_client = NULL; - core->watch_remove(channel->stream->watch); - channel->stream->watch = NULL; - reds_stream_free(channel->stream); - channel->stream = NULL; - spice_marshaller_destroy(channel->send_data.marshaller); - snd_channel_put(channel); - worker->connection = NULL; -} - -static void snd_playback_free_frame(PlaybackChannel *playback_channel, AudioFrame *frame) -{ - frame->channel = playback_channel; - frame->next = playback_channel->free_frames; - playback_channel->free_frames = frame; -} - -static void snd_playback_on_message_done(SndChannel *channel) -{ - PlaybackChannel *playback_channel = (PlaybackChannel *)channel; - if (playback_channel->in_progress) { - snd_playback_free_frame(playback_channel, playback_channel->in_progress); - playback_channel->in_progress = NULL; - if (playback_channel->pending_frame) { - channel->command |= SND_PLAYBACK_PCM_MASK; - } - } -} - -static void snd_record_on_message_done(SndChannel *channel) -{ -} - -static int snd_send_data(SndChannel *channel) -{ - uint32_t n; - - if (!channel) { - return FALSE; - } - - if (!(n = channel->send_data.size - channel->send_data.pos)) { - return TRUE; - } - - for (;;) { - struct iovec vec[IOV_MAX]; - int vec_size; - - if (!n) { - channel->on_message_done(channel); - - if (channel->blocked) { - channel->blocked = FALSE; - core->watch_update_mask(channel->stream->watch, SPICE_WATCH_EVENT_READ); - } - break; - } - - vec_size = spice_marshaller_fill_iovec(channel->send_data.marshaller, - vec, IOV_MAX, channel->send_data.pos); - n = reds_stream_writev(channel->stream, vec, vec_size); - if (n == -1) { - switch (errno) { - case EAGAIN: - channel->blocked = TRUE; - core->watch_update_mask(channel->stream->watch, SPICE_WATCH_EVENT_READ | - SPICE_WATCH_EVENT_WRITE); - return FALSE; - case EINTR: - break; - case EPIPE: - snd_disconnect_channel(channel); - return FALSE; - default: - spice_printerr("%s", strerror(errno)); - snd_disconnect_channel(channel); - return FALSE; - } - } else { - channel->send_data.pos += n; - } - n = channel->send_data.size - channel->send_data.pos; - } - return TRUE; -} - -static int snd_record_handle_write(RecordChannel *record_channel, size_t size, void *message) -{ - SpiceMsgcRecordPacket *packet; - uint32_t write_pos; - uint32_t* data; - uint32_t len; - uint32_t now; - - if (!record_channel) { - return FALSE; - } - - packet = (SpiceMsgcRecordPacket *)message; - - if (record_channel->mode == SPICE_AUDIO_DATA_MODE_RAW) { - data = (uint32_t *)packet->data; - size = packet->data_size >> 2; - size = MIN(size, RECORD_SAMPLES_SIZE); - } else { - int decode_size; - decode_size = sizeof(record_channel->decode_buf); - if (snd_codec_decode(record_channel->codec, packet->data, packet->data_size, - record_channel->decode_buf, &decode_size) != SND_CODEC_OK) - return FALSE; - data = (uint32_t *) record_channel->decode_buf; - size = decode_size >> 2; - } - - write_pos = record_channel->write_pos % RECORD_SAMPLES_SIZE; - record_channel->write_pos += size; - len = RECORD_SAMPLES_SIZE - write_pos; - now = MIN(len, size); - size -= now; - memcpy(record_channel->samples + write_pos, data, now << 2); - - if (size) { - memcpy(record_channel->samples, data + now, size << 2); - } - - if (record_channel->write_pos - record_channel->read_pos > RECORD_SAMPLES_SIZE) { - record_channel->read_pos = record_channel->write_pos - RECORD_SAMPLES_SIZE; - } - return TRUE; -} - -static int snd_playback_handle_message(SndChannel *channel, size_t size, uint32_t type, void *message) -{ - if (!channel) { - return FALSE; - } - - switch (type) { - case SPICE_MSGC_DISCONNECTING: - break; - default: - spice_printerr("invalid message type %u", type); - return FALSE; - } - return TRUE; -} - -static int snd_record_handle_message(SndChannel *channel, size_t size, uint32_t type, void *message) -{ - RecordChannel *record_channel = (RecordChannel *)channel; - - if (!channel) { - return FALSE; - } - switch (type) { - case SPICE_MSGC_RECORD_DATA: - return snd_record_handle_write((RecordChannel *)channel, size, message); - case SPICE_MSGC_RECORD_MODE: { - SpiceMsgcRecordMode *mode = (SpiceMsgcRecordMode *)message; - SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker); - record_channel->mode_time = mode->time; - if (mode->mode != SPICE_AUDIO_DATA_MODE_RAW) { - if (snd_codec_is_capable(mode->mode, st->frequency)) { - if (snd_codec_create(&record_channel->codec, mode->mode, st->frequency, SND_CODEC_DECODE) == SND_CODEC_OK) { - record_channel->mode = mode->mode; - } else { - spice_printerr("create decoder failed"); - return FALSE; - } - } - else { - spice_printerr("unsupported mode %d", record_channel->mode); - return FALSE; - } - } - else - record_channel->mode = mode->mode; - break; - } - - case SPICE_MSGC_RECORD_START_MARK: { - SpiceMsgcRecordStartMark *mark = (SpiceMsgcRecordStartMark *)message; - record_channel->start_time = mark->time; - break; - } - case SPICE_MSGC_DISCONNECTING: - break; - default: - spice_printerr("invalid message type %u", type); - return FALSE; - } - return TRUE; -} - -static void snd_receive(void* data) -{ - SndChannel *channel = (SndChannel*)data; - SpiceDataHeaderOpaque *header; - - if (!channel) { - return; - } - - header = &channel->channel_client->incoming.header; - - for (;;) { - ssize_t n; - n = channel->receive_data.end - channel->receive_data.now; - spice_warn_if(n <= 0); - n = reds_stream_read(channel->stream, channel->receive_data.now, n); - if (n <= 0) { - if (n == 0) { - snd_disconnect_channel(channel); - return; - } - spice_assert(n == -1); - switch (errno) { - case EAGAIN: - return; - case EINTR: - break; - case EPIPE: - snd_disconnect_channel(channel); - return; - default: - spice_printerr("%s", strerror(errno)); - snd_disconnect_channel(channel); - return; - } - } else { - channel->receive_data.now += n; - for (;;) { - uint8_t *msg_start = channel->receive_data.message_start; - uint8_t *data = msg_start + header->header_size; - size_t parsed_size; - uint8_t *parsed; - message_destructor_t parsed_free; - - header->data = msg_start; - n = channel->receive_data.now - msg_start; - - if (n < header->header_size || - n < header->header_size + header->get_msg_size(header)) { - break; - } - parsed = channel->parser((void *)data, data + header->get_msg_size(header), - header->get_msg_type(header), - SPICE_VERSION_MINOR, &parsed_size, &parsed_free); - if (parsed == NULL) { - spice_printerr("failed to parse message type %d", header->get_msg_type(header)); - snd_disconnect_channel(channel); - return; - } - if (!channel->handle_message(channel, parsed_size, - header->get_msg_type(header), parsed)) { - free(parsed); - snd_disconnect_channel(channel); - return; - } - parsed_free(parsed); - channel->receive_data.message_start = msg_start + header->header_size + - header->get_msg_size(header); - } - if (channel->receive_data.now == channel->receive_data.message_start) { - channel->receive_data.now = channel->receive_data.buf; - channel->receive_data.message_start = channel->receive_data.buf; - } else if (channel->receive_data.now == channel->receive_data.end) { - memcpy(channel->receive_data.buf, channel->receive_data.message_start, n); - channel->receive_data.now = channel->receive_data.buf + n; - channel->receive_data.message_start = channel->receive_data.buf; - } - } - } -} - -static void snd_event(int fd, int event, void *data) -{ - SndChannel *channel = data; - - if (event & SPICE_WATCH_EVENT_READ) { - snd_receive(channel); - } - if (event & SPICE_WATCH_EVENT_WRITE) { - channel->send_messages(channel); - } -} - -static inline int snd_reset_send_data(SndChannel *channel, uint16_t verb) -{ - SpiceDataHeaderOpaque *header; - - if (!channel) { - return FALSE; - } - - header = &channel->channel_client->send_data.header; - spice_marshaller_reset(channel->send_data.marshaller); - header->data = spice_marshaller_reserve_space(channel->send_data.marshaller, - header->header_size); - spice_marshaller_set_base(channel->send_data.marshaller, - header->header_size); - channel->send_data.pos = 0; - header->set_msg_size(header, 0); - header->set_msg_type(header, verb); - channel->send_data.serial++; - if (!channel->channel_client->is_mini_header) { - header->set_msg_serial(header, channel->send_data.serial); - header->set_msg_sub_list(header, 0); - } - - return TRUE; -} - -static int snd_begin_send_message(SndChannel *channel) -{ - SpiceDataHeaderOpaque *header = &channel->channel_client->send_data.header; - - spice_marshaller_flush(channel->send_data.marshaller); - channel->send_data.size = spice_marshaller_get_total_size(channel->send_data.marshaller); - header->set_msg_size(header, channel->send_data.size - header->header_size); - return snd_send_data(channel); -} - -static int snd_channel_send_migrate(SndChannel *channel) -{ - SpiceMsgMigrate migrate; - - if (!snd_reset_send_data(channel, SPICE_MSG_MIGRATE)) { - return FALSE; - } - spice_debug(NULL); - migrate.flags = 0; - spice_marshall_msg_migrate(channel->send_data.marshaller, &migrate); - - return snd_begin_send_message(channel); -} - -static int snd_playback_send_migrate(PlaybackChannel *channel) -{ - return snd_channel_send_migrate(&channel->base); -} - -static int snd_send_volume(SndChannel *channel, SpiceVolumeState *st, int msg) -{ - SpiceMsgAudioVolume *vol; - uint8_t c; - - vol = alloca(sizeof (SpiceMsgAudioVolume) + - st->volume_nchannels * sizeof (uint16_t)); - if (!snd_reset_send_data(channel, msg)) { - return FALSE; - } - vol->nchannels = st->volume_nchannels; - for (c = 0; c < st->volume_nchannels; ++c) { - vol->volume[c] = st->volume[c]; - } - spice_marshall_SpiceMsgAudioVolume(channel->send_data.marshaller, vol); - - return snd_begin_send_message(channel); -} - -static int snd_playback_send_volume(PlaybackChannel *playback_channel) -{ - SndChannel *channel = &playback_channel->base; - SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker); - - if (!red_channel_client_test_remote_cap(channel->channel_client, - SPICE_PLAYBACK_CAP_VOLUME)) { - return TRUE; - } - - return snd_send_volume(channel, &st->volume, SPICE_MSG_PLAYBACK_VOLUME); -} - -static int snd_send_mute(SndChannel *channel, SpiceVolumeState *st, int msg) -{ - SpiceMsgAudioMute mute; - - if (!snd_reset_send_data(channel, msg)) { - return FALSE; - } - mute.mute = st->mute; - spice_marshall_SpiceMsgAudioMute(channel->send_data.marshaller, &mute); - - return snd_begin_send_message(channel); -} - -static int snd_playback_send_mute(PlaybackChannel *playback_channel) -{ - SndChannel *channel = &playback_channel->base; - SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker); - - if (!red_channel_client_test_remote_cap(channel->channel_client, - SPICE_PLAYBACK_CAP_VOLUME)) { - return TRUE; - } - - return snd_send_mute(channel, &st->volume, SPICE_MSG_PLAYBACK_MUTE); -} - -static int snd_playback_send_latency(PlaybackChannel *playback_channel) -{ - SndChannel *channel = &playback_channel->base; - SpiceMsgPlaybackLatency latency_msg; - - spice_debug("latency %u", playback_channel->latency); - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_LATENCY)) { - return FALSE; - } - latency_msg.latency_ms = playback_channel->latency; - spice_marshall_msg_playback_latency(channel->send_data.marshaller, &latency_msg); - - return snd_begin_send_message(channel); -} -static int snd_playback_send_start(PlaybackChannel *playback_channel) -{ - SndChannel *channel = (SndChannel *)playback_channel; - SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker); - SpiceMsgPlaybackStart start; - - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_START)) { - return FALSE; - } - - start.channels = SPICE_INTERFACE_PLAYBACK_CHAN; - start.frequency = st->frequency; - spice_assert(SPICE_INTERFACE_PLAYBACK_FMT == SPICE_INTERFACE_AUDIO_FMT_S16); - start.format = SPICE_AUDIO_FMT_S16; - start.time = reds_get_mm_time(); - spice_marshall_msg_playback_start(channel->send_data.marshaller, &start); - - return snd_begin_send_message(channel); -} - -static int snd_playback_send_stop(PlaybackChannel *playback_channel) -{ - SndChannel *channel = (SndChannel *)playback_channel; - - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_STOP)) { - return FALSE; - } - - return snd_begin_send_message(channel); -} - -static int snd_playback_send_ctl(PlaybackChannel *playback_channel) -{ - SndChannel *channel = (SndChannel *)playback_channel; - - if ((channel->client_active = channel->active)) { - return snd_playback_send_start(playback_channel); - } else { - return snd_playback_send_stop(playback_channel); - } -} - -static int snd_record_send_start(RecordChannel *record_channel) -{ - SndChannel *channel = (SndChannel *)record_channel; - SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker); - SpiceMsgRecordStart start; - - if (!snd_reset_send_data(channel, SPICE_MSG_RECORD_START)) { - return FALSE; - } - - start.channels = SPICE_INTERFACE_RECORD_CHAN; - start.frequency = st->frequency; - spice_assert(SPICE_INTERFACE_RECORD_FMT == SPICE_INTERFACE_AUDIO_FMT_S16); - start.format = SPICE_AUDIO_FMT_S16; - spice_marshall_msg_record_start(channel->send_data.marshaller, &start); - - return snd_begin_send_message(channel); -} - -static int snd_record_send_stop(RecordChannel *record_channel) -{ - SndChannel *channel = (SndChannel *)record_channel; - - if (!snd_reset_send_data(channel, SPICE_MSG_RECORD_STOP)) { - return FALSE; - } - - return snd_begin_send_message(channel); -} - -static int snd_record_send_ctl(RecordChannel *record_channel) -{ - SndChannel *channel = (SndChannel *)record_channel; - - if ((channel->client_active = channel->active)) { - return snd_record_send_start(record_channel); - } else { - return snd_record_send_stop(record_channel); - } -} - -static int snd_record_send_volume(RecordChannel *record_channel) -{ - SndChannel *channel = &record_channel->base; - SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker); - - if (!red_channel_client_test_remote_cap(channel->channel_client, - SPICE_RECORD_CAP_VOLUME)) { - return TRUE; - } - - return snd_send_volume(channel, &st->volume, SPICE_MSG_RECORD_VOLUME); -} - -static int snd_record_send_mute(RecordChannel *record_channel) -{ - SndChannel *channel = &record_channel->base; - SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker); - - if (!red_channel_client_test_remote_cap(channel->channel_client, - SPICE_RECORD_CAP_VOLUME)) { - return TRUE; - } - - return snd_send_mute(channel, &st->volume, SPICE_MSG_RECORD_MUTE); -} - -static int snd_record_send_migrate(RecordChannel *record_channel) -{ - /* No need for migration data: if recording has started before migration, - * the client receives RECORD_STOP from the src before the migration completion - * notification (when the vm is stopped). - * Afterwards, when the vm starts on the dest, the client receives RECORD_START. */ - return snd_channel_send_migrate(&record_channel->base); -} - -static int snd_playback_send_write(PlaybackChannel *playback_channel) -{ - SndChannel *channel = (SndChannel *)playback_channel; - AudioFrame *frame; - SpiceMsgPlaybackPacket msg; - - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_DATA)) { - return FALSE; - } - - frame = playback_channel->in_progress; - msg.time = frame->time; - - spice_marshall_msg_playback_data(channel->send_data.marshaller, &msg); - - if (playback_channel->mode == SPICE_AUDIO_DATA_MODE_RAW) { - spice_marshaller_add_ref(channel->send_data.marshaller, - (uint8_t *)frame->samples, - snd_codec_frame_size(playback_channel->codec) * sizeof(frame->samples[0])); - } - else { - int n = sizeof(playback_channel->encode_buf); - if (snd_codec_encode(playback_channel->codec, (uint8_t *) frame->samples, - snd_codec_frame_size(playback_channel->codec) * sizeof(frame->samples[0]), - playback_channel->encode_buf, &n) != SND_CODEC_OK) { - spice_printerr("encode failed"); - snd_disconnect_channel(channel); - return FALSE; - } - spice_marshaller_add_ref(channel->send_data.marshaller, playback_channel->encode_buf, n); - } - - return snd_begin_send_message(channel); -} - -static int playback_send_mode(PlaybackChannel *playback_channel) -{ - SndChannel *channel = (SndChannel *)playback_channel; - SpiceMsgPlaybackMode mode; - - if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_MODE)) { - return FALSE; - } - mode.time = reds_get_mm_time(); - mode.mode = playback_channel->mode; - spice_marshall_msg_playback_mode(channel->send_data.marshaller, &mode); - - return snd_begin_send_message(channel); -} - -static void snd_playback_send(void* data) -{ - PlaybackChannel *playback_channel = (PlaybackChannel*)data; - SndChannel *channel = (SndChannel*)playback_channel; - - if (!playback_channel || !snd_send_data(data)) { - return; - } - - while (channel->command) { - if (channel->command & SND_PLAYBACK_MODE_MASK) { - if (!playback_send_mode(playback_channel)) { - return; - } - channel->command &= ~SND_PLAYBACK_MODE_MASK; - } - if (channel->command & SND_PLAYBACK_PCM_MASK) { - spice_assert(!playback_channel->in_progress && playback_channel->pending_frame); - playback_channel->in_progress = playback_channel->pending_frame; - playback_channel->pending_frame = NULL; - channel->command &= ~SND_PLAYBACK_PCM_MASK; - if (!snd_playback_send_write(playback_channel)) { - spice_printerr("snd_send_playback_write failed"); - return; - } - } - if (channel->command & SND_PLAYBACK_CTRL_MASK) { - if (!snd_playback_send_ctl(playback_channel)) { - return; - } - channel->command &= ~SND_PLAYBACK_CTRL_MASK; - } - if (channel->command & SND_PLAYBACK_VOLUME_MASK) { - if (!snd_playback_send_volume(playback_channel) || - !snd_playback_send_mute(playback_channel)) { - return; - } - channel->command &= ~SND_PLAYBACK_VOLUME_MASK; - } - if (channel->command & SND_PLAYBACK_MIGRATE_MASK) { - if (!snd_playback_send_migrate(playback_channel)) { - return; - } - channel->command &= ~SND_PLAYBACK_MIGRATE_MASK; - } - if (channel->command & SND_PLAYBACK_LATENCY_MASK) { - if (!snd_playback_send_latency(playback_channel)) { - return; - } - channel->command &= ~SND_PLAYBACK_LATENCY_MASK; - } - } -} - -static void snd_record_send(void* data) -{ - RecordChannel *record_channel = (RecordChannel*)data; - SndChannel *channel = (SndChannel*)record_channel; - - if (!record_channel || !snd_send_data(data)) { - return; - } - - while (channel->command) { - if (channel->command & SND_RECORD_CTRL_MASK) { - if (!snd_record_send_ctl(record_channel)) { - return; - } - channel->command &= ~SND_RECORD_CTRL_MASK; - } - if (channel->command & SND_RECORD_VOLUME_MASK) { - if (!snd_record_send_volume(record_channel) || - !snd_record_send_mute(record_channel)) { - return; - } - channel->command &= ~SND_RECORD_VOLUME_MASK; - } - if (channel->command & SND_RECORD_MIGRATE_MASK) { - if (!snd_record_send_migrate(record_channel)) { - return; - } - channel->command &= ~SND_RECORD_MIGRATE_MASK; - } - } -} - -static SndChannel *__new_channel(SndWorker *worker, int size, uint32_t channel_id, - RedClient *client, - RedsStream *stream, - int migrate, - snd_channel_send_messages_proc send_messages, - snd_channel_handle_message_proc handle_message, - snd_channel_on_message_done_proc on_message_done, - snd_channel_cleanup_channel_proc cleanup, - uint32_t *common_caps, int num_common_caps, - uint32_t *caps, int num_caps) -{ - SndChannel *channel; - int delay_val; - int flags; -#ifdef SO_PRIORITY - int priority; -#endif - int tos; - MainChannelClient *mcc = red_client_get_main(client); - - spice_assert(stream); - if ((flags = fcntl(stream->socket, F_GETFL)) == -1) { - spice_printerr("accept failed, %s", strerror(errno)); - goto error1; - } - -#ifdef SO_PRIORITY - priority = 6; - if (setsockopt(stream->socket, SOL_SOCKET, SO_PRIORITY, (void*)&priority, - sizeof(priority)) == -1) { - if (errno != ENOTSUP) { - spice_printerr("setsockopt failed, %s", strerror(errno)); - } - } -#endif - - tos = IPTOS_LOWDELAY; - if (setsockopt(stream->socket, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) == -1) { - if (errno != ENOTSUP) { - spice_printerr("setsockopt failed, %s", strerror(errno)); - } - } - - delay_val = main_channel_client_is_low_bandwidth(mcc) ? 0 : 1; - if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) { - if (errno != ENOTSUP) { - spice_printerr("setsockopt failed, %s", strerror(errno)); - } - } - - if (fcntl(stream->socket, F_SETFL, flags | O_NONBLOCK) == -1) { - spice_printerr("accept failed, %s", strerror(errno)); - goto error1; - } - - spice_assert(size >= sizeof(*channel)); - channel = spice_malloc0(size); - channel->refs = 1; - channel->parser = spice_get_client_channel_parser(channel_id, NULL); - channel->stream = stream; - channel->worker = worker; - channel->receive_data.message_start = channel->receive_data.buf; - channel->receive_data.now = channel->receive_data.buf; - channel->receive_data.end = channel->receive_data.buf + sizeof(channel->receive_data.buf); - channel->send_data.marshaller = spice_marshaller_new(); - - stream->watch = core->watch_add(stream->socket, SPICE_WATCH_EVENT_READ, - snd_event, channel); - if (stream->watch == NULL) { - spice_printerr("watch_add failed, %s", strerror(errno)); - goto error2; - } - - channel->send_messages = send_messages; - channel->handle_message = handle_message; - channel->on_message_done = on_message_done; - channel->cleanup = cleanup; - - channel->channel_client = red_channel_client_create_dummy(sizeof(RedChannelClient), - worker->base_channel, - client, - num_common_caps, common_caps, - num_caps, caps); - if (!channel->channel_client) { - goto error2; - } - return channel; - -error2: - free(channel); - -error1: - reds_stream_free(stream); - return NULL; -} - -static void snd_disconnect_channel_client(RedChannelClient *rcc) -{ - SndWorker *worker; - - spice_assert(rcc->channel); - spice_assert(rcc->channel->data); - worker = (SndWorker *)rcc->channel->data; - - spice_debug("channel-type=%d", rcc->channel->type); - if (worker->connection) { - spice_assert(worker->connection->channel_client == rcc); - snd_disconnect_channel(worker->connection); - } -} - -static void snd_set_command(SndChannel *channel, uint32_t command) -{ - if (!channel) { - return; - } - channel->command |= command; -} - -SPICE_GNUC_VISIBLE void spice_server_playback_set_volume(SpicePlaybackInstance *sin, - uint8_t nchannels, - uint16_t *volume) -{ - SpiceVolumeState *st = &sin->st->volume; - SndChannel *channel = sin->st->worker.connection; - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); - - st->volume_nchannels = nchannels; - free(st->volume); - st->volume = spice_memdup(volume, sizeof(uint16_t) * nchannels); - - if (!channel || nchannels == 0) - return; - - snd_playback_send_volume(playback_channel); -} - -SPICE_GNUC_VISIBLE void spice_server_playback_set_mute(SpicePlaybackInstance *sin, uint8_t mute) -{ - SpiceVolumeState *st = &sin->st->volume; - SndChannel *channel = sin->st->worker.connection; - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); - - st->mute = mute; - - if (!channel) - return; - - snd_playback_send_mute(playback_channel); -} - -SPICE_GNUC_VISIBLE void spice_server_playback_start(SpicePlaybackInstance *sin) -{ - SndChannel *channel = sin->st->worker.connection; - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); - - sin->st->worker.active = 1; - if (!channel) - return; - spice_assert(!playback_channel->base.active); - reds_disable_mm_time(); - playback_channel->base.active = TRUE; - if (!playback_channel->base.client_active) { - snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK); - snd_playback_send(&playback_channel->base); - } else { - playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK; - } -} - -SPICE_GNUC_VISIBLE void spice_server_playback_stop(SpicePlaybackInstance *sin) -{ - SndChannel *channel = sin->st->worker.connection; - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); - - sin->st->worker.active = 0; - if (!channel) - return; - spice_assert(playback_channel->base.active); - reds_enable_mm_time(); - playback_channel->base.active = FALSE; - if (playback_channel->base.client_active) { - snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK); - snd_playback_send(&playback_channel->base); - } else { - playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK; - playback_channel->base.command &= ~SND_PLAYBACK_PCM_MASK; - - if (playback_channel->pending_frame) { - spice_assert(!playback_channel->in_progress); - snd_playback_free_frame(playback_channel, - playback_channel->pending_frame); - playback_channel->pending_frame = NULL; - } - } -} - -SPICE_GNUC_VISIBLE void spice_server_playback_get_buffer(SpicePlaybackInstance *sin, - uint32_t **frame, uint32_t *num_samples) -{ - SndChannel *channel = sin->st->worker.connection; - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); - - if (!channel || !playback_channel->free_frames) { - *frame = NULL; - *num_samples = 0; - return; - } - spice_assert(playback_channel->base.active); - snd_channel_get(channel); - - *frame = playback_channel->free_frames->samples; - playback_channel->free_frames = playback_channel->free_frames->next; - *num_samples = snd_codec_frame_size(playback_channel->codec); -} - -SPICE_GNUC_VISIBLE void spice_server_playback_put_samples(SpicePlaybackInstance *sin, uint32_t *samples) -{ - PlaybackChannel *playback_channel; - AudioFrame *frame; - - frame = SPICE_CONTAINEROF(samples, AudioFrame, samples); - playback_channel = frame->channel; - spice_assert(playback_channel); - if (!snd_channel_put(&playback_channel->base) || - sin->st->worker.connection != &playback_channel->base) { - /* lost last reference, channel has been destroyed previously */ - spice_info("audio samples belong to a disconnected channel"); - return; - } - spice_assert(playback_channel->base.active); - - if (playback_channel->pending_frame) { - snd_playback_free_frame(playback_channel, playback_channel->pending_frame); - } - frame->time = reds_get_mm_time(); - playback_channel->pending_frame = frame; - snd_set_command(&playback_channel->base, SND_PLAYBACK_PCM_MASK); - snd_playback_send(&playback_channel->base); -} - -void snd_set_playback_latency(RedClient *client, uint32_t latency) -{ - SndWorker *now = workers; - - for (; now; now = now->next) { - if (now->base_channel->type == SPICE_CHANNEL_PLAYBACK && now->connection && - now->connection->channel_client->client == client) { - - if (red_channel_client_test_remote_cap(now->connection->channel_client, - SPICE_PLAYBACK_CAP_LATENCY)) { - PlaybackChannel* playback = (PlaybackChannel*)now->connection; - - playback->latency = latency; - snd_set_command(now->connection, SND_PLAYBACK_LATENCY_MASK); - snd_playback_send(now->connection); - } else { - spice_debug("client doesn't not support SPICE_PLAYBACK_CAP_LATENCY"); - } - } - } -} - -static int snd_desired_audio_mode(int frequency, int client_can_celt, int client_can_opus) -{ - if (! playback_compression) - return SPICE_AUDIO_DATA_MODE_RAW; - - if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency)) - return SPICE_AUDIO_DATA_MODE_OPUS; - - if (client_can_celt && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, frequency)) - return SPICE_AUDIO_DATA_MODE_CELT_0_5_1; - - return SPICE_AUDIO_DATA_MODE_RAW; -} - -static void on_new_playback_channel(SndWorker *worker) -{ - PlaybackChannel *playback_channel = - SPICE_CONTAINEROF(worker->connection, PlaybackChannel, base); - SpicePlaybackState *st = SPICE_CONTAINEROF(worker, SpicePlaybackState, worker); - - spice_assert(playback_channel); - - snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_MODE_MASK); - if (playback_channel->base.active) { - snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_CTRL_MASK); - } - if (st->volume.volume_nchannels) { - snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_VOLUME_MASK); - } - if (playback_channel->base.active) { - reds_disable_mm_time(); - } -} - -static void snd_playback_cleanup(SndChannel *channel) -{ - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); - - if (playback_channel->base.active) { - reds_enable_mm_time(); - } - - snd_codec_destroy(&playback_channel->codec); -} - -static void snd_set_playback_peer(RedChannel *channel, RedClient *client, RedsStream *stream, - int migration, int num_common_caps, uint32_t *common_caps, - int num_caps, uint32_t *caps) -{ - SndWorker *worker = channel->data; - PlaybackChannel *playback_channel; - SpicePlaybackState *st = SPICE_CONTAINEROF(worker, SpicePlaybackState, worker); - - snd_disconnect_channel(worker->connection); - - if (!(playback_channel = (PlaybackChannel *)__new_channel(worker, - sizeof(*playback_channel), - SPICE_CHANNEL_PLAYBACK, - client, - stream, - migration, - snd_playback_send, - snd_playback_handle_message, - snd_playback_on_message_done, - snd_playback_cleanup, - common_caps, num_common_caps, - caps, num_caps))) { - return; - } - worker->connection = &playback_channel->base; - snd_playback_free_frame(playback_channel, &playback_channel->frames[0]); - snd_playback_free_frame(playback_channel, &playback_channel->frames[1]); - snd_playback_free_frame(playback_channel, &playback_channel->frames[2]); - - int client_can_celt = red_channel_client_test_remote_cap(playback_channel->base.channel_client, - SPICE_PLAYBACK_CAP_CELT_0_5_1); - int client_can_opus = red_channel_client_test_remote_cap(playback_channel->base.channel_client, - SPICE_PLAYBACK_CAP_OPUS); - int desired_mode = snd_desired_audio_mode(st->frequency, client_can_celt, client_can_opus); - playback_channel->mode = SPICE_AUDIO_DATA_MODE_RAW; - if (desired_mode != SPICE_AUDIO_DATA_MODE_RAW) { - if (snd_codec_create(&playback_channel->codec, desired_mode, st->frequency, SND_CODEC_ENCODE) == SND_CODEC_OK) { - playback_channel->mode = desired_mode; - } else { - spice_printerr("create encoder failed"); - } - } - - if (!red_client_during_migrate_at_target(client)) { - on_new_playback_channel(worker); - } - - if (worker->active) { - spice_server_playback_start(st->sin); - } - snd_playback_send(worker->connection); -} - -static void snd_record_migrate_channel_client(RedChannelClient *rcc) -{ - SndWorker *worker; - - spice_debug(NULL); - spice_assert(rcc->channel); - spice_assert(rcc->channel->data); - worker = (SndWorker *)rcc->channel->data; - - if (worker->connection) { - spice_assert(worker->connection->channel_client == rcc); - snd_set_command(worker->connection, SND_RECORD_MIGRATE_MASK); - snd_record_send(worker->connection); - } -} - -SPICE_GNUC_VISIBLE void spice_server_record_set_volume(SpiceRecordInstance *sin, - uint8_t nchannels, - uint16_t *volume) -{ - SpiceVolumeState *st = &sin->st->volume; - SndChannel *channel = sin->st->worker.connection; - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); - - st->volume_nchannels = nchannels; - free(st->volume); - st->volume = spice_memdup(volume, sizeof(uint16_t) * nchannels); - - if (!channel || nchannels == 0) - return; - - snd_record_send_volume(record_channel); -} - -SPICE_GNUC_VISIBLE void spice_server_record_set_mute(SpiceRecordInstance *sin, uint8_t mute) -{ - SpiceVolumeState *st = &sin->st->volume; - SndChannel *channel = sin->st->worker.connection; - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); - - st->mute = mute; - - if (!channel) - return; - - snd_record_send_mute(record_channel); -} - -SPICE_GNUC_VISIBLE void spice_server_record_start(SpiceRecordInstance *sin) -{ - SndChannel *channel = sin->st->worker.connection; - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); - - sin->st->worker.active = 1; - if (!channel) - return; - spice_assert(!record_channel->base.active); - record_channel->base.active = TRUE; - record_channel->read_pos = record_channel->write_pos = 0; //todo: improve by - //stream generation - if (!record_channel->base.client_active) { - snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK); - snd_record_send(&record_channel->base); - } else { - record_channel->base.command &= ~SND_RECORD_CTRL_MASK; - } -} - -SPICE_GNUC_VISIBLE void spice_server_record_stop(SpiceRecordInstance *sin) -{ - SndChannel *channel = sin->st->worker.connection; - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); - - sin->st->worker.active = 0; - if (!channel) - return; - spice_assert(record_channel->base.active); - record_channel->base.active = FALSE; - if (record_channel->base.client_active) { - snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK); - snd_record_send(&record_channel->base); - } else { - record_channel->base.command &= ~SND_RECORD_CTRL_MASK; - } -} - -SPICE_GNUC_VISIBLE uint32_t spice_server_record_get_samples(SpiceRecordInstance *sin, - uint32_t *samples, uint32_t bufsize) -{ - SndChannel *channel = sin->st->worker.connection; - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); - uint32_t read_pos; - uint32_t now; - uint32_t len; - - if (!channel) - return 0; - spice_assert(record_channel->base.active); - - if (record_channel->write_pos < RECORD_SAMPLES_SIZE / 2) { - return 0; - } - - len = MIN(record_channel->write_pos - record_channel->read_pos, bufsize); - - if (len < bufsize) { - SndWorker *worker = record_channel->base.worker; - snd_receive(record_channel); - if (!worker->connection) { - return 0; - } - len = MIN(record_channel->write_pos - record_channel->read_pos, bufsize); - } - - read_pos = record_channel->read_pos % RECORD_SAMPLES_SIZE; - record_channel->read_pos += len; - now = MIN(len, RECORD_SAMPLES_SIZE - read_pos); - memcpy(samples, &record_channel->samples[read_pos], now * 4); - if (now < len) { - memcpy(samples + now, record_channel->samples, (len - now) * 4); - } - return len; -} - -SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_playback_rate(SpicePlaybackInstance *sin) -{ - int client_can_opus = TRUE; - if (sin && sin->st->worker.connection) { - SndChannel *channel = sin->st->worker.connection; - PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); - client_can_opus = red_channel_client_test_remote_cap(playback_channel->base.channel_client, - SPICE_PLAYBACK_CAP_OPUS); - } - - if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY)) - return SND_CODEC_OPUS_PLAYBACK_FREQ; - - return SND_CODEC_CELT_PLAYBACK_FREQ; -} - -SPICE_GNUC_VISIBLE void spice_server_set_playback_rate(SpicePlaybackInstance *sin, uint32_t frequency) -{ - RedChannel *channel = sin->st->worker.base_channel; - sin->st->frequency = frequency; - if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency)) - red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_OPUS); -} - -SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_record_rate(SpiceRecordInstance *sin) -{ - int client_can_opus = TRUE; - if (sin && sin->st->worker.connection) { - SndChannel *channel = sin->st->worker.connection; - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); - client_can_opus = red_channel_client_test_remote_cap(record_channel->base.channel_client, - SPICE_RECORD_CAP_OPUS); - } - - if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY)) - return SND_CODEC_OPUS_PLAYBACK_FREQ; - - return SND_CODEC_CELT_PLAYBACK_FREQ; -} - -SPICE_GNUC_VISIBLE void spice_server_set_record_rate(SpiceRecordInstance *sin, uint32_t frequency) -{ - RedChannel *channel = sin->st->worker.base_channel; - sin->st->frequency = frequency; - if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency)) - red_channel_set_cap(channel, SPICE_RECORD_CAP_OPUS); -} - -static void on_new_record_channel(SndWorker *worker) -{ - RecordChannel *record_channel = (RecordChannel *)worker->connection; - SpiceRecordState *st = SPICE_CONTAINEROF(worker, SpiceRecordState, worker); - - spice_assert(record_channel); - - if (st->volume.volume_nchannels) { - snd_set_command((SndChannel *)record_channel, SND_RECORD_VOLUME_MASK); - } - if (record_channel->base.active) { - snd_set_command((SndChannel *)record_channel, SND_RECORD_CTRL_MASK); - } -} - -static void snd_record_cleanup(SndChannel *channel) -{ - RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); - snd_codec_destroy(&record_channel->codec); -} - -static void snd_set_record_peer(RedChannel *channel, RedClient *client, RedsStream *stream, - int migration, int num_common_caps, uint32_t *common_caps, - int num_caps, uint32_t *caps) -{ - SndWorker *worker = channel->data; - RecordChannel *record_channel; - SpiceRecordState *st = SPICE_CONTAINEROF(worker, SpiceRecordState, worker); - - snd_disconnect_channel(worker->connection); - - if (!(record_channel = (RecordChannel *)__new_channel(worker, - sizeof(*record_channel), - SPICE_CHANNEL_RECORD, - client, - stream, - migration, - snd_record_send, - snd_record_handle_message, - snd_record_on_message_done, - snd_record_cleanup, - common_caps, num_common_caps, - caps, num_caps))) { - return; - } - - record_channel->mode = SPICE_AUDIO_DATA_MODE_RAW; - - worker->connection = &record_channel->base; - - on_new_record_channel(worker); - if (worker->active) { - spice_server_record_start(st->sin); - } - snd_record_send(worker->connection); -} - -static void snd_playback_migrate_channel_client(RedChannelClient *rcc) -{ - SndWorker *worker; - - spice_assert(rcc->channel); - spice_assert(rcc->channel->data); - worker = (SndWorker *)rcc->channel->data; - spice_debug(NULL); - - if (worker->connection) { - spice_assert(worker->connection->channel_client == rcc); - snd_set_command(worker->connection, SND_PLAYBACK_MIGRATE_MASK); - snd_playback_send(worker->connection); - } -} - -static void add_worker(SndWorker *worker) -{ - worker->next = workers; - workers = worker; -} - -static void remove_worker(SndWorker *worker) -{ - SndWorker **now = &workers; - while (*now) { - if (*now == worker) { - *now = worker->next; - return; - } - now = &(*now)->next; - } - spice_printerr("not found"); -} - -void snd_attach_playback(SpicePlaybackInstance *sin) -{ - SndWorker *playback_worker; - RedChannel *channel; - ClientCbs client_cbs = { NULL, }; - - sin->st = spice_new0(SpicePlaybackState, 1); - sin->st->sin = sin; - playback_worker = &sin->st->worker; - sin->st->frequency = SND_CODEC_CELT_PLAYBACK_FREQ; /* Default to the legacy rate */ - - // TODO: Make RedChannel base of worker? instead of assigning it to channel->data - channel = red_channel_create_dummy(sizeof(RedChannel), SPICE_CHANNEL_PLAYBACK, 0); - - channel->data = playback_worker; - client_cbs.connect = snd_set_playback_peer; - client_cbs.disconnect = snd_disconnect_channel_client; - client_cbs.migrate = snd_playback_migrate_channel_client; - red_channel_register_client_cbs(channel, &client_cbs); - red_channel_set_data(channel, playback_worker); - - if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY)) - red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_CELT_0_5_1); - - red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_VOLUME); - - playback_worker->base_channel = channel; - add_worker(playback_worker); - reds_register_channel(playback_worker->base_channel); -} - -void snd_attach_record(SpiceRecordInstance *sin) -{ - SndWorker *record_worker; - RedChannel *channel; - ClientCbs client_cbs = { NULL, }; - - sin->st = spice_new0(SpiceRecordState, 1); - sin->st->sin = sin; - record_worker = &sin->st->worker; - sin->st->frequency = SND_CODEC_CELT_PLAYBACK_FREQ; /* Default to the legacy rate */ - - // TODO: Make RedChannel base of worker? instead of assigning it to channel->data - channel = red_channel_create_dummy(sizeof(RedChannel), SPICE_CHANNEL_RECORD, 0); - - channel->data = record_worker; - client_cbs.connect = snd_set_record_peer; - client_cbs.disconnect = snd_disconnect_channel_client; - client_cbs.migrate = snd_record_migrate_channel_client; - red_channel_register_client_cbs(channel, &client_cbs); - red_channel_set_data(channel, record_worker); - if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY)) - red_channel_set_cap(channel, SPICE_RECORD_CAP_CELT_0_5_1); - red_channel_set_cap(channel, SPICE_RECORD_CAP_VOLUME); - - record_worker->base_channel = channel; - add_worker(record_worker); - reds_register_channel(record_worker->base_channel); -} - -static void snd_detach_common(SndWorker *worker) -{ - if (!worker) { - return; - } - remove_worker(worker); - snd_disconnect_channel(worker->connection); - reds_unregister_channel(worker->base_channel); - red_channel_destroy(worker->base_channel); -} - -static void spice_playback_state_free(SpicePlaybackState *st) -{ - free(st->volume.volume); - free(st); -} - -void snd_detach_playback(SpicePlaybackInstance *sin) -{ - snd_detach_common(&sin->st->worker); - spice_playback_state_free(sin->st); -} - -static void spice_record_state_free(SpiceRecordState *st) -{ - free(st->volume.volume); - free(st); -} - -void snd_detach_record(SpiceRecordInstance *sin) -{ - snd_detach_common(&sin->st->worker); - spice_record_state_free(sin->st); -} - -void snd_set_playback_compression(int on) -{ - SndWorker *now = workers; - - playback_compression = !!on; - - for (; now; now = now->next) { - if (now->base_channel->type == SPICE_CHANNEL_PLAYBACK && now->connection) { - PlaybackChannel* playback = (PlaybackChannel*)now->connection; - SpicePlaybackState *st = SPICE_CONTAINEROF(now, SpicePlaybackState, worker); - int client_can_celt = red_channel_client_test_remote_cap(playback->base.channel_client, - SPICE_PLAYBACK_CAP_CELT_0_5_1); - int client_can_opus = red_channel_client_test_remote_cap(playback->base.channel_client, - SPICE_PLAYBACK_CAP_OPUS); - int desired_mode = snd_desired_audio_mode(st->frequency, client_can_opus, client_can_celt); - if (playback->mode != desired_mode) { - playback->mode = desired_mode; - snd_set_command(now->connection, SND_PLAYBACK_MODE_MASK); - } - } - } -} diff --git a/server/snd_worker.h b/server/snd_worker.h deleted file mode 100644 index 7cc4db5..0000000 --- a/server/snd_worker.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef _H_SND_WORKER -#define _H_SND_WORKER - -#include "spice.h" - -void snd_attach_playback(SpicePlaybackInstance *sin); -void snd_detach_playback(SpicePlaybackInstance *sin); - -void snd_attach_record(SpiceRecordInstance *sin); -void snd_detach_record(SpiceRecordInstance *sin); - -void snd_set_playback_compression(int on); - -void snd_set_playback_latency(RedClient *client, uint32_t latency); - -#endif diff --git a/server/sound.c b/server/sound.c new file mode 100644 index 0000000..a91d56e --- /dev/null +++ b/server/sound.c @@ -0,0 +1,1625 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <errno.h> +#include <limits.h> +#include <sys/socket.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#include "common/marshaller.h" +#include "common/generated_server_marshallers.h" + +#include "spice.h" +#include "red_common.h" +#include "main-channel.h" +#include "reds.h" +#include "red_dispatcher.h" +#include "sound.h" +#include "common/snd_codec.h" +#include "demarshallers.h" + +#ifndef IOV_MAX +#define IOV_MAX 1024 +#endif + +#define SND_RECEIVE_BUF_SIZE (16 * 1024 * 2) +#define RECORD_SAMPLES_SIZE (SND_RECEIVE_BUF_SIZE >> 2) + +enum PlaybackCommand { + SND_PLAYBACK_MIGRATE, + SND_PLAYBACK_MODE, + SND_PLAYBACK_CTRL, + SND_PLAYBACK_PCM, + SND_PLAYBACK_VOLUME, + SND_PLAYBACK_LATENCY, +}; + +enum RecordCommand { + SND_RECORD_MIGRATE, + SND_RECORD_CTRL, + SND_RECORD_VOLUME, +}; + +#define SND_PLAYBACK_MIGRATE_MASK (1 << SND_PLAYBACK_MIGRATE) +#define SND_PLAYBACK_MODE_MASK (1 << SND_PLAYBACK_MODE) +#define SND_PLAYBACK_CTRL_MASK (1 << SND_PLAYBACK_CTRL) +#define SND_PLAYBACK_PCM_MASK (1 << SND_PLAYBACK_PCM) +#define SND_PLAYBACK_VOLUME_MASK (1 << SND_PLAYBACK_VOLUME) +#define SND_PLAYBACK_LATENCY_MASK ( 1 << SND_PLAYBACK_LATENCY) + +#define SND_RECORD_MIGRATE_MASK (1 << SND_RECORD_MIGRATE) +#define SND_RECORD_CTRL_MASK (1 << SND_RECORD_CTRL) +#define SND_RECORD_VOLUME_MASK (1 << SND_RECORD_VOLUME) + +typedef struct SndChannel SndChannel; +typedef void (*snd_channel_send_messages_proc)(void *in_channel); +typedef int (*snd_channel_handle_message_proc)(SndChannel *channel, size_t size, uint32_t type, void *message); +typedef void (*snd_channel_on_message_done_proc)(SndChannel *channel); +typedef void (*snd_channel_cleanup_channel_proc)(SndChannel *channel); + +typedef struct SndWorker SndWorker; + +struct SndChannel { + RedsStream *stream; + SndWorker *worker; + spice_parse_channel_func_t parser; + int refs; + + RedChannelClient *channel_client; + + int active; + int client_active; + int blocked; + + uint32_t command; + uint32_t ack_generation; + uint32_t client_ack_generation; + uint32_t out_messages; + uint32_t ack_messages; + + struct { + uint64_t serial; + SpiceMarshaller *marshaller; + uint32_t size; + uint32_t pos; + } send_data; + + struct { + uint8_t buf[SND_RECEIVE_BUF_SIZE]; + uint8_t *message_start; + uint8_t *now; + uint8_t *end; + } receive_data; + + snd_channel_send_messages_proc send_messages; + snd_channel_handle_message_proc handle_message; + snd_channel_on_message_done_proc on_message_done; + snd_channel_cleanup_channel_proc cleanup; +}; + +typedef struct PlaybackChannel PlaybackChannel; + +typedef struct AudioFrame AudioFrame; +struct AudioFrame { + uint32_t time; + uint32_t samples[SND_CODEC_MAX_FRAME_SIZE]; + PlaybackChannel *channel; + AudioFrame *next; +}; + +struct PlaybackChannel { + SndChannel base; + AudioFrame frames[3]; + AudioFrame *free_frames; + AudioFrame *in_progress; + AudioFrame *pending_frame; + uint32_t mode; + uint32_t latency; + SndCodec codec; + uint8_t encode_buf[SND_CODEC_MAX_COMPRESSED_BYTES]; +}; + +struct SndWorker { + RedChannel *base_channel; + SndChannel *connection; + SndWorker *next; + int active; +}; + +typedef struct SpiceVolumeState { + uint8_t volume_nchannels; + uint16_t *volume; + int mute; +} SpiceVolumeState; + +struct SpicePlaybackState { + struct SndWorker worker; + SpicePlaybackInstance *sin; + SpiceVolumeState volume; + uint32_t frequency; +}; + +struct SpiceRecordState { + struct SndWorker worker; + SpiceRecordInstance *sin; + SpiceVolumeState volume; + uint32_t frequency; +}; + +typedef struct RecordChannel { + SndChannel base; + uint32_t samples[RECORD_SAMPLES_SIZE]; + uint32_t write_pos; + uint32_t read_pos; + uint32_t mode; + uint32_t mode_time; + uint32_t start_time; + SndCodec codec; + uint8_t decode_buf[SND_CODEC_MAX_FRAME_BYTES]; +} RecordChannel; + +static SndWorker *workers; +static uint32_t playback_compression = TRUE; + +static void snd_receive(void* data); + +static SndChannel *snd_channel_get(SndChannel *channel) +{ + channel->refs++; + return channel; +} + +static SndChannel *snd_channel_put(SndChannel *channel) +{ + if (!--channel->refs) { + spice_printerr("SndChannel=%p freed", channel); + free(channel); + return NULL; + } + return channel; +} + +static void snd_disconnect_channel(SndChannel *channel) +{ + SndWorker *worker; + + if (!channel || !channel->stream) { + spice_debug("not connected"); + return; + } + spice_debug("SndChannel=%p rcc=%p type=%d", + channel, channel->channel_client, channel->channel_client->channel->type); + worker = channel->worker; + channel->cleanup(channel); + red_channel_client_disconnect(worker->connection->channel_client); + worker->connection->channel_client = NULL; + core->watch_remove(channel->stream->watch); + channel->stream->watch = NULL; + reds_stream_free(channel->stream); + channel->stream = NULL; + spice_marshaller_destroy(channel->send_data.marshaller); + snd_channel_put(channel); + worker->connection = NULL; +} + +static void snd_playback_free_frame(PlaybackChannel *playback_channel, AudioFrame *frame) +{ + frame->channel = playback_channel; + frame->next = playback_channel->free_frames; + playback_channel->free_frames = frame; +} + +static void snd_playback_on_message_done(SndChannel *channel) +{ + PlaybackChannel *playback_channel = (PlaybackChannel *)channel; + if (playback_channel->in_progress) { + snd_playback_free_frame(playback_channel, playback_channel->in_progress); + playback_channel->in_progress = NULL; + if (playback_channel->pending_frame) { + channel->command |= SND_PLAYBACK_PCM_MASK; + } + } +} + +static void snd_record_on_message_done(SndChannel *channel) +{ +} + +static int snd_send_data(SndChannel *channel) +{ + uint32_t n; + + if (!channel) { + return FALSE; + } + + if (!(n = channel->send_data.size - channel->send_data.pos)) { + return TRUE; + } + + for (;;) { + struct iovec vec[IOV_MAX]; + int vec_size; + + if (!n) { + channel->on_message_done(channel); + + if (channel->blocked) { + channel->blocked = FALSE; + core->watch_update_mask(channel->stream->watch, SPICE_WATCH_EVENT_READ); + } + break; + } + + vec_size = spice_marshaller_fill_iovec(channel->send_data.marshaller, + vec, IOV_MAX, channel->send_data.pos); + n = reds_stream_writev(channel->stream, vec, vec_size); + if (n == -1) { + switch (errno) { + case EAGAIN: + channel->blocked = TRUE; + core->watch_update_mask(channel->stream->watch, SPICE_WATCH_EVENT_READ | + SPICE_WATCH_EVENT_WRITE); + return FALSE; + case EINTR: + break; + case EPIPE: + snd_disconnect_channel(channel); + return FALSE; + default: + spice_printerr("%s", strerror(errno)); + snd_disconnect_channel(channel); + return FALSE; + } + } else { + channel->send_data.pos += n; + } + n = channel->send_data.size - channel->send_data.pos; + } + return TRUE; +} + +static int snd_record_handle_write(RecordChannel *record_channel, size_t size, void *message) +{ + SpiceMsgcRecordPacket *packet; + uint32_t write_pos; + uint32_t* data; + uint32_t len; + uint32_t now; + + if (!record_channel) { + return FALSE; + } + + packet = (SpiceMsgcRecordPacket *)message; + + if (record_channel->mode == SPICE_AUDIO_DATA_MODE_RAW) { + data = (uint32_t *)packet->data; + size = packet->data_size >> 2; + size = MIN(size, RECORD_SAMPLES_SIZE); + } else { + int decode_size; + decode_size = sizeof(record_channel->decode_buf); + if (snd_codec_decode(record_channel->codec, packet->data, packet->data_size, + record_channel->decode_buf, &decode_size) != SND_CODEC_OK) + return FALSE; + data = (uint32_t *) record_channel->decode_buf; + size = decode_size >> 2; + } + + write_pos = record_channel->write_pos % RECORD_SAMPLES_SIZE; + record_channel->write_pos += size; + len = RECORD_SAMPLES_SIZE - write_pos; + now = MIN(len, size); + size -= now; + memcpy(record_channel->samples + write_pos, data, now << 2); + + if (size) { + memcpy(record_channel->samples, data + now, size << 2); + } + + if (record_channel->write_pos - record_channel->read_pos > RECORD_SAMPLES_SIZE) { + record_channel->read_pos = record_channel->write_pos - RECORD_SAMPLES_SIZE; + } + return TRUE; +} + +static int snd_playback_handle_message(SndChannel *channel, size_t size, uint32_t type, void *message) +{ + if (!channel) { + return FALSE; + } + + switch (type) { + case SPICE_MSGC_DISCONNECTING: + break; + default: + spice_printerr("invalid message type %u", type); + return FALSE; + } + return TRUE; +} + +static int snd_record_handle_message(SndChannel *channel, size_t size, uint32_t type, void *message) +{ + RecordChannel *record_channel = (RecordChannel *)channel; + + if (!channel) { + return FALSE; + } + switch (type) { + case SPICE_MSGC_RECORD_DATA: + return snd_record_handle_write((RecordChannel *)channel, size, message); + case SPICE_MSGC_RECORD_MODE: { + SpiceMsgcRecordMode *mode = (SpiceMsgcRecordMode *)message; + SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker); + record_channel->mode_time = mode->time; + if (mode->mode != SPICE_AUDIO_DATA_MODE_RAW) { + if (snd_codec_is_capable(mode->mode, st->frequency)) { + if (snd_codec_create(&record_channel->codec, mode->mode, st->frequency, SND_CODEC_DECODE) == SND_CODEC_OK) { + record_channel->mode = mode->mode; + } else { + spice_printerr("create decoder failed"); + return FALSE; + } + } + else { + spice_printerr("unsupported mode %d", record_channel->mode); + return FALSE; + } + } + else + record_channel->mode = mode->mode; + break; + } + + case SPICE_MSGC_RECORD_START_MARK: { + SpiceMsgcRecordStartMark *mark = (SpiceMsgcRecordStartMark *)message; + record_channel->start_time = mark->time; + break; + } + case SPICE_MSGC_DISCONNECTING: + break; + default: + spice_printerr("invalid message type %u", type); + return FALSE; + } + return TRUE; +} + +static void snd_receive(void* data) +{ + SndChannel *channel = (SndChannel*)data; + SpiceDataHeaderOpaque *header; + + if (!channel) { + return; + } + + header = &channel->channel_client->incoming.header; + + for (;;) { + ssize_t n; + n = channel->receive_data.end - channel->receive_data.now; + spice_warn_if(n <= 0); + n = reds_stream_read(channel->stream, channel->receive_data.now, n); + if (n <= 0) { + if (n == 0) { + snd_disconnect_channel(channel); + return; + } + spice_assert(n == -1); + switch (errno) { + case EAGAIN: + return; + case EINTR: + break; + case EPIPE: + snd_disconnect_channel(channel); + return; + default: + spice_printerr("%s", strerror(errno)); + snd_disconnect_channel(channel); + return; + } + } else { + channel->receive_data.now += n; + for (;;) { + uint8_t *msg_start = channel->receive_data.message_start; + uint8_t *data = msg_start + header->header_size; + size_t parsed_size; + uint8_t *parsed; + message_destructor_t parsed_free; + + header->data = msg_start; + n = channel->receive_data.now - msg_start; + + if (n < header->header_size || + n < header->header_size + header->get_msg_size(header)) { + break; + } + parsed = channel->parser((void *)data, data + header->get_msg_size(header), + header->get_msg_type(header), + SPICE_VERSION_MINOR, &parsed_size, &parsed_free); + if (parsed == NULL) { + spice_printerr("failed to parse message type %d", header->get_msg_type(header)); + snd_disconnect_channel(channel); + return; + } + if (!channel->handle_message(channel, parsed_size, + header->get_msg_type(header), parsed)) { + free(parsed); + snd_disconnect_channel(channel); + return; + } + parsed_free(parsed); + channel->receive_data.message_start = msg_start + header->header_size + + header->get_msg_size(header); + } + if (channel->receive_data.now == channel->receive_data.message_start) { + channel->receive_data.now = channel->receive_data.buf; + channel->receive_data.message_start = channel->receive_data.buf; + } else if (channel->receive_data.now == channel->receive_data.end) { + memcpy(channel->receive_data.buf, channel->receive_data.message_start, n); + channel->receive_data.now = channel->receive_data.buf + n; + channel->receive_data.message_start = channel->receive_data.buf; + } + } + } +} + +static void snd_event(int fd, int event, void *data) +{ + SndChannel *channel = data; + + if (event & SPICE_WATCH_EVENT_READ) { + snd_receive(channel); + } + if (event & SPICE_WATCH_EVENT_WRITE) { + channel->send_messages(channel); + } +} + +static inline int snd_reset_send_data(SndChannel *channel, uint16_t verb) +{ + SpiceDataHeaderOpaque *header; + + if (!channel) { + return FALSE; + } + + header = &channel->channel_client->send_data.header; + spice_marshaller_reset(channel->send_data.marshaller); + header->data = spice_marshaller_reserve_space(channel->send_data.marshaller, + header->header_size); + spice_marshaller_set_base(channel->send_data.marshaller, + header->header_size); + channel->send_data.pos = 0; + header->set_msg_size(header, 0); + header->set_msg_type(header, verb); + channel->send_data.serial++; + if (!channel->channel_client->is_mini_header) { + header->set_msg_serial(header, channel->send_data.serial); + header->set_msg_sub_list(header, 0); + } + + return TRUE; +} + +static int snd_begin_send_message(SndChannel *channel) +{ + SpiceDataHeaderOpaque *header = &channel->channel_client->send_data.header; + + spice_marshaller_flush(channel->send_data.marshaller); + channel->send_data.size = spice_marshaller_get_total_size(channel->send_data.marshaller); + header->set_msg_size(header, channel->send_data.size - header->header_size); + return snd_send_data(channel); +} + +static int snd_channel_send_migrate(SndChannel *channel) +{ + SpiceMsgMigrate migrate; + + if (!snd_reset_send_data(channel, SPICE_MSG_MIGRATE)) { + return FALSE; + } + spice_debug(NULL); + migrate.flags = 0; + spice_marshall_msg_migrate(channel->send_data.marshaller, &migrate); + + return snd_begin_send_message(channel); +} + +static int snd_playback_send_migrate(PlaybackChannel *channel) +{ + return snd_channel_send_migrate(&channel->base); +} + +static int snd_send_volume(SndChannel *channel, SpiceVolumeState *st, int msg) +{ + SpiceMsgAudioVolume *vol; + uint8_t c; + + vol = alloca(sizeof (SpiceMsgAudioVolume) + + st->volume_nchannels * sizeof (uint16_t)); + if (!snd_reset_send_data(channel, msg)) { + return FALSE; + } + vol->nchannels = st->volume_nchannels; + for (c = 0; c < st->volume_nchannels; ++c) { + vol->volume[c] = st->volume[c]; + } + spice_marshall_SpiceMsgAudioVolume(channel->send_data.marshaller, vol); + + return snd_begin_send_message(channel); +} + +static int snd_playback_send_volume(PlaybackChannel *playback_channel) +{ + SndChannel *channel = &playback_channel->base; + SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker); + + if (!red_channel_client_test_remote_cap(channel->channel_client, + SPICE_PLAYBACK_CAP_VOLUME)) { + return TRUE; + } + + return snd_send_volume(channel, &st->volume, SPICE_MSG_PLAYBACK_VOLUME); +} + +static int snd_send_mute(SndChannel *channel, SpiceVolumeState *st, int msg) +{ + SpiceMsgAudioMute mute; + + if (!snd_reset_send_data(channel, msg)) { + return FALSE; + } + mute.mute = st->mute; + spice_marshall_SpiceMsgAudioMute(channel->send_data.marshaller, &mute); + + return snd_begin_send_message(channel); +} + +static int snd_playback_send_mute(PlaybackChannel *playback_channel) +{ + SndChannel *channel = &playback_channel->base; + SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker); + + if (!red_channel_client_test_remote_cap(channel->channel_client, + SPICE_PLAYBACK_CAP_VOLUME)) { + return TRUE; + } + + return snd_send_mute(channel, &st->volume, SPICE_MSG_PLAYBACK_MUTE); +} + +static int snd_playback_send_latency(PlaybackChannel *playback_channel) +{ + SndChannel *channel = &playback_channel->base; + SpiceMsgPlaybackLatency latency_msg; + + spice_debug("latency %u", playback_channel->latency); + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_LATENCY)) { + return FALSE; + } + latency_msg.latency_ms = playback_channel->latency; + spice_marshall_msg_playback_latency(channel->send_data.marshaller, &latency_msg); + + return snd_begin_send_message(channel); +} +static int snd_playback_send_start(PlaybackChannel *playback_channel) +{ + SndChannel *channel = (SndChannel *)playback_channel; + SpicePlaybackState *st = SPICE_CONTAINEROF(channel->worker, SpicePlaybackState, worker); + SpiceMsgPlaybackStart start; + + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_START)) { + return FALSE; + } + + start.channels = SPICE_INTERFACE_PLAYBACK_CHAN; + start.frequency = st->frequency; + spice_assert(SPICE_INTERFACE_PLAYBACK_FMT == SPICE_INTERFACE_AUDIO_FMT_S16); + start.format = SPICE_AUDIO_FMT_S16; + start.time = reds_get_mm_time(); + spice_marshall_msg_playback_start(channel->send_data.marshaller, &start); + + return snd_begin_send_message(channel); +} + +static int snd_playback_send_stop(PlaybackChannel *playback_channel) +{ + SndChannel *channel = (SndChannel *)playback_channel; + + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_STOP)) { + return FALSE; + } + + return snd_begin_send_message(channel); +} + +static int snd_playback_send_ctl(PlaybackChannel *playback_channel) +{ + SndChannel *channel = (SndChannel *)playback_channel; + + if ((channel->client_active = channel->active)) { + return snd_playback_send_start(playback_channel); + } else { + return snd_playback_send_stop(playback_channel); + } +} + +static int snd_record_send_start(RecordChannel *record_channel) +{ + SndChannel *channel = (SndChannel *)record_channel; + SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker); + SpiceMsgRecordStart start; + + if (!snd_reset_send_data(channel, SPICE_MSG_RECORD_START)) { + return FALSE; + } + + start.channels = SPICE_INTERFACE_RECORD_CHAN; + start.frequency = st->frequency; + spice_assert(SPICE_INTERFACE_RECORD_FMT == SPICE_INTERFACE_AUDIO_FMT_S16); + start.format = SPICE_AUDIO_FMT_S16; + spice_marshall_msg_record_start(channel->send_data.marshaller, &start); + + return snd_begin_send_message(channel); +} + +static int snd_record_send_stop(RecordChannel *record_channel) +{ + SndChannel *channel = (SndChannel *)record_channel; + + if (!snd_reset_send_data(channel, SPICE_MSG_RECORD_STOP)) { + return FALSE; + } + + return snd_begin_send_message(channel); +} + +static int snd_record_send_ctl(RecordChannel *record_channel) +{ + SndChannel *channel = (SndChannel *)record_channel; + + if ((channel->client_active = channel->active)) { + return snd_record_send_start(record_channel); + } else { + return snd_record_send_stop(record_channel); + } +} + +static int snd_record_send_volume(RecordChannel *record_channel) +{ + SndChannel *channel = &record_channel->base; + SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker); + + if (!red_channel_client_test_remote_cap(channel->channel_client, + SPICE_RECORD_CAP_VOLUME)) { + return TRUE; + } + + return snd_send_volume(channel, &st->volume, SPICE_MSG_RECORD_VOLUME); +} + +static int snd_record_send_mute(RecordChannel *record_channel) +{ + SndChannel *channel = &record_channel->base; + SpiceRecordState *st = SPICE_CONTAINEROF(channel->worker, SpiceRecordState, worker); + + if (!red_channel_client_test_remote_cap(channel->channel_client, + SPICE_RECORD_CAP_VOLUME)) { + return TRUE; + } + + return snd_send_mute(channel, &st->volume, SPICE_MSG_RECORD_MUTE); +} + +static int snd_record_send_migrate(RecordChannel *record_channel) +{ + /* No need for migration data: if recording has started before migration, + * the client receives RECORD_STOP from the src before the migration completion + * notification (when the vm is stopped). + * Afterwards, when the vm starts on the dest, the client receives RECORD_START. */ + return snd_channel_send_migrate(&record_channel->base); +} + +static int snd_playback_send_write(PlaybackChannel *playback_channel) +{ + SndChannel *channel = (SndChannel *)playback_channel; + AudioFrame *frame; + SpiceMsgPlaybackPacket msg; + + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_DATA)) { + return FALSE; + } + + frame = playback_channel->in_progress; + msg.time = frame->time; + + spice_marshall_msg_playback_data(channel->send_data.marshaller, &msg); + + if (playback_channel->mode == SPICE_AUDIO_DATA_MODE_RAW) { + spice_marshaller_add_ref(channel->send_data.marshaller, + (uint8_t *)frame->samples, + snd_codec_frame_size(playback_channel->codec) * sizeof(frame->samples[0])); + } + else { + int n = sizeof(playback_channel->encode_buf); + if (snd_codec_encode(playback_channel->codec, (uint8_t *) frame->samples, + snd_codec_frame_size(playback_channel->codec) * sizeof(frame->samples[0]), + playback_channel->encode_buf, &n) != SND_CODEC_OK) { + spice_printerr("encode failed"); + snd_disconnect_channel(channel); + return FALSE; + } + spice_marshaller_add_ref(channel->send_data.marshaller, playback_channel->encode_buf, n); + } + + return snd_begin_send_message(channel); +} + +static int playback_send_mode(PlaybackChannel *playback_channel) +{ + SndChannel *channel = (SndChannel *)playback_channel; + SpiceMsgPlaybackMode mode; + + if (!snd_reset_send_data(channel, SPICE_MSG_PLAYBACK_MODE)) { + return FALSE; + } + mode.time = reds_get_mm_time(); + mode.mode = playback_channel->mode; + spice_marshall_msg_playback_mode(channel->send_data.marshaller, &mode); + + return snd_begin_send_message(channel); +} + +static void snd_playback_send(void* data) +{ + PlaybackChannel *playback_channel = (PlaybackChannel*)data; + SndChannel *channel = (SndChannel*)playback_channel; + + if (!playback_channel || !snd_send_data(data)) { + return; + } + + while (channel->command) { + if (channel->command & SND_PLAYBACK_MODE_MASK) { + if (!playback_send_mode(playback_channel)) { + return; + } + channel->command &= ~SND_PLAYBACK_MODE_MASK; + } + if (channel->command & SND_PLAYBACK_PCM_MASK) { + spice_assert(!playback_channel->in_progress && playback_channel->pending_frame); + playback_channel->in_progress = playback_channel->pending_frame; + playback_channel->pending_frame = NULL; + channel->command &= ~SND_PLAYBACK_PCM_MASK; + if (!snd_playback_send_write(playback_channel)) { + spice_printerr("snd_send_playback_write failed"); + return; + } + } + if (channel->command & SND_PLAYBACK_CTRL_MASK) { + if (!snd_playback_send_ctl(playback_channel)) { + return; + } + channel->command &= ~SND_PLAYBACK_CTRL_MASK; + } + if (channel->command & SND_PLAYBACK_VOLUME_MASK) { + if (!snd_playback_send_volume(playback_channel) || + !snd_playback_send_mute(playback_channel)) { + return; + } + channel->command &= ~SND_PLAYBACK_VOLUME_MASK; + } + if (channel->command & SND_PLAYBACK_MIGRATE_MASK) { + if (!snd_playback_send_migrate(playback_channel)) { + return; + } + channel->command &= ~SND_PLAYBACK_MIGRATE_MASK; + } + if (channel->command & SND_PLAYBACK_LATENCY_MASK) { + if (!snd_playback_send_latency(playback_channel)) { + return; + } + channel->command &= ~SND_PLAYBACK_LATENCY_MASK; + } + } +} + +static void snd_record_send(void* data) +{ + RecordChannel *record_channel = (RecordChannel*)data; + SndChannel *channel = (SndChannel*)record_channel; + + if (!record_channel || !snd_send_data(data)) { + return; + } + + while (channel->command) { + if (channel->command & SND_RECORD_CTRL_MASK) { + if (!snd_record_send_ctl(record_channel)) { + return; + } + channel->command &= ~SND_RECORD_CTRL_MASK; + } + if (channel->command & SND_RECORD_VOLUME_MASK) { + if (!snd_record_send_volume(record_channel) || + !snd_record_send_mute(record_channel)) { + return; + } + channel->command &= ~SND_RECORD_VOLUME_MASK; + } + if (channel->command & SND_RECORD_MIGRATE_MASK) { + if (!snd_record_send_migrate(record_channel)) { + return; + } + channel->command &= ~SND_RECORD_MIGRATE_MASK; + } + } +} + +static SndChannel *__new_channel(SndWorker *worker, int size, uint32_t channel_id, + RedClient *client, + RedsStream *stream, + int migrate, + snd_channel_send_messages_proc send_messages, + snd_channel_handle_message_proc handle_message, + snd_channel_on_message_done_proc on_message_done, + snd_channel_cleanup_channel_proc cleanup, + uint32_t *common_caps, int num_common_caps, + uint32_t *caps, int num_caps) +{ + SndChannel *channel; + int delay_val; + int flags; +#ifdef SO_PRIORITY + int priority; +#endif + int tos; + MainChannelClient *mcc = red_client_get_main(client); + + spice_assert(stream); + if ((flags = fcntl(stream->socket, F_GETFL)) == -1) { + spice_printerr("accept failed, %s", strerror(errno)); + goto error1; + } + +#ifdef SO_PRIORITY + priority = 6; + if (setsockopt(stream->socket, SOL_SOCKET, SO_PRIORITY, (void*)&priority, + sizeof(priority)) == -1) { + if (errno != ENOTSUP) { + spice_printerr("setsockopt failed, %s", strerror(errno)); + } + } +#endif + + tos = IPTOS_LOWDELAY; + if (setsockopt(stream->socket, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) == -1) { + if (errno != ENOTSUP) { + spice_printerr("setsockopt failed, %s", strerror(errno)); + } + } + + delay_val = main_channel_client_is_low_bandwidth(mcc) ? 0 : 1; + if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) { + if (errno != ENOTSUP) { + spice_printerr("setsockopt failed, %s", strerror(errno)); + } + } + + if (fcntl(stream->socket, F_SETFL, flags | O_NONBLOCK) == -1) { + spice_printerr("accept failed, %s", strerror(errno)); + goto error1; + } + + spice_assert(size >= sizeof(*channel)); + channel = spice_malloc0(size); + channel->refs = 1; + channel->parser = spice_get_client_channel_parser(channel_id, NULL); + channel->stream = stream; + channel->worker = worker; + channel->receive_data.message_start = channel->receive_data.buf; + channel->receive_data.now = channel->receive_data.buf; + channel->receive_data.end = channel->receive_data.buf + sizeof(channel->receive_data.buf); + channel->send_data.marshaller = spice_marshaller_new(); + + stream->watch = core->watch_add(stream->socket, SPICE_WATCH_EVENT_READ, + snd_event, channel); + if (stream->watch == NULL) { + spice_printerr("watch_add failed, %s", strerror(errno)); + goto error2; + } + + channel->send_messages = send_messages; + channel->handle_message = handle_message; + channel->on_message_done = on_message_done; + channel->cleanup = cleanup; + + channel->channel_client = red_channel_client_create_dummy(sizeof(RedChannelClient), + worker->base_channel, + client, + num_common_caps, common_caps, + num_caps, caps); + if (!channel->channel_client) { + goto error2; + } + return channel; + +error2: + free(channel); + +error1: + reds_stream_free(stream); + return NULL; +} + +static void snd_disconnect_channel_client(RedChannelClient *rcc) +{ + SndWorker *worker; + + spice_assert(rcc->channel); + spice_assert(rcc->channel->data); + worker = (SndWorker *)rcc->channel->data; + + spice_debug("channel-type=%d", rcc->channel->type); + if (worker->connection) { + spice_assert(worker->connection->channel_client == rcc); + snd_disconnect_channel(worker->connection); + } +} + +static void snd_set_command(SndChannel *channel, uint32_t command) +{ + if (!channel) { + return; + } + channel->command |= command; +} + +SPICE_GNUC_VISIBLE void spice_server_playback_set_volume(SpicePlaybackInstance *sin, + uint8_t nchannels, + uint16_t *volume) +{ + SpiceVolumeState *st = &sin->st->volume; + SndChannel *channel = sin->st->worker.connection; + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); + + st->volume_nchannels = nchannels; + free(st->volume); + st->volume = spice_memdup(volume, sizeof(uint16_t) * nchannels); + + if (!channel || nchannels == 0) + return; + + snd_playback_send_volume(playback_channel); +} + +SPICE_GNUC_VISIBLE void spice_server_playback_set_mute(SpicePlaybackInstance *sin, uint8_t mute) +{ + SpiceVolumeState *st = &sin->st->volume; + SndChannel *channel = sin->st->worker.connection; + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); + + st->mute = mute; + + if (!channel) + return; + + snd_playback_send_mute(playback_channel); +} + +SPICE_GNUC_VISIBLE void spice_server_playback_start(SpicePlaybackInstance *sin) +{ + SndChannel *channel = sin->st->worker.connection; + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); + + sin->st->worker.active = 1; + if (!channel) + return; + spice_assert(!playback_channel->base.active); + reds_disable_mm_time(); + playback_channel->base.active = TRUE; + if (!playback_channel->base.client_active) { + snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK); + snd_playback_send(&playback_channel->base); + } else { + playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK; + } +} + +SPICE_GNUC_VISIBLE void spice_server_playback_stop(SpicePlaybackInstance *sin) +{ + SndChannel *channel = sin->st->worker.connection; + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); + + sin->st->worker.active = 0; + if (!channel) + return; + spice_assert(playback_channel->base.active); + reds_enable_mm_time(); + playback_channel->base.active = FALSE; + if (playback_channel->base.client_active) { + snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK); + snd_playback_send(&playback_channel->base); + } else { + playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK; + playback_channel->base.command &= ~SND_PLAYBACK_PCM_MASK; + + if (playback_channel->pending_frame) { + spice_assert(!playback_channel->in_progress); + snd_playback_free_frame(playback_channel, + playback_channel->pending_frame); + playback_channel->pending_frame = NULL; + } + } +} + +SPICE_GNUC_VISIBLE void spice_server_playback_get_buffer(SpicePlaybackInstance *sin, + uint32_t **frame, uint32_t *num_samples) +{ + SndChannel *channel = sin->st->worker.connection; + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); + + if (!channel || !playback_channel->free_frames) { + *frame = NULL; + *num_samples = 0; + return; + } + spice_assert(playback_channel->base.active); + snd_channel_get(channel); + + *frame = playback_channel->free_frames->samples; + playback_channel->free_frames = playback_channel->free_frames->next; + *num_samples = snd_codec_frame_size(playback_channel->codec); +} + +SPICE_GNUC_VISIBLE void spice_server_playback_put_samples(SpicePlaybackInstance *sin, uint32_t *samples) +{ + PlaybackChannel *playback_channel; + AudioFrame *frame; + + frame = SPICE_CONTAINEROF(samples, AudioFrame, samples); + playback_channel = frame->channel; + spice_assert(playback_channel); + if (!snd_channel_put(&playback_channel->base) || + sin->st->worker.connection != &playback_channel->base) { + /* lost last reference, channel has been destroyed previously */ + spice_info("audio samples belong to a disconnected channel"); + return; + } + spice_assert(playback_channel->base.active); + + if (playback_channel->pending_frame) { + snd_playback_free_frame(playback_channel, playback_channel->pending_frame); + } + frame->time = reds_get_mm_time(); + playback_channel->pending_frame = frame; + snd_set_command(&playback_channel->base, SND_PLAYBACK_PCM_MASK); + snd_playback_send(&playback_channel->base); +} + +void snd_set_playback_latency(RedClient *client, uint32_t latency) +{ + SndWorker *now = workers; + + for (; now; now = now->next) { + if (now->base_channel->type == SPICE_CHANNEL_PLAYBACK && now->connection && + now->connection->channel_client->client == client) { + + if (red_channel_client_test_remote_cap(now->connection->channel_client, + SPICE_PLAYBACK_CAP_LATENCY)) { + PlaybackChannel* playback = (PlaybackChannel*)now->connection; + + playback->latency = latency; + snd_set_command(now->connection, SND_PLAYBACK_LATENCY_MASK); + snd_playback_send(now->connection); + } else { + spice_debug("client doesn't not support SPICE_PLAYBACK_CAP_LATENCY"); + } + } + } +} + +static int snd_desired_audio_mode(int frequency, int client_can_celt, int client_can_opus) +{ + if (! playback_compression) + return SPICE_AUDIO_DATA_MODE_RAW; + + if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency)) + return SPICE_AUDIO_DATA_MODE_OPUS; + + if (client_can_celt && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, frequency)) + return SPICE_AUDIO_DATA_MODE_CELT_0_5_1; + + return SPICE_AUDIO_DATA_MODE_RAW; +} + +static void on_new_playback_channel(SndWorker *worker) +{ + PlaybackChannel *playback_channel = + SPICE_CONTAINEROF(worker->connection, PlaybackChannel, base); + SpicePlaybackState *st = SPICE_CONTAINEROF(worker, SpicePlaybackState, worker); + + spice_assert(playback_channel); + + snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_MODE_MASK); + if (playback_channel->base.active) { + snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_CTRL_MASK); + } + if (st->volume.volume_nchannels) { + snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_VOLUME_MASK); + } + if (playback_channel->base.active) { + reds_disable_mm_time(); + } +} + +static void snd_playback_cleanup(SndChannel *channel) +{ + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); + + if (playback_channel->base.active) { + reds_enable_mm_time(); + } + + snd_codec_destroy(&playback_channel->codec); +} + +static void snd_set_playback_peer(RedChannel *channel, RedClient *client, RedsStream *stream, + int migration, int num_common_caps, uint32_t *common_caps, + int num_caps, uint32_t *caps) +{ + SndWorker *worker = channel->data; + PlaybackChannel *playback_channel; + SpicePlaybackState *st = SPICE_CONTAINEROF(worker, SpicePlaybackState, worker); + + snd_disconnect_channel(worker->connection); + + if (!(playback_channel = (PlaybackChannel *)__new_channel(worker, + sizeof(*playback_channel), + SPICE_CHANNEL_PLAYBACK, + client, + stream, + migration, + snd_playback_send, + snd_playback_handle_message, + snd_playback_on_message_done, + snd_playback_cleanup, + common_caps, num_common_caps, + caps, num_caps))) { + return; + } + worker->connection = &playback_channel->base; + snd_playback_free_frame(playback_channel, &playback_channel->frames[0]); + snd_playback_free_frame(playback_channel, &playback_channel->frames[1]); + snd_playback_free_frame(playback_channel, &playback_channel->frames[2]); + + int client_can_celt = red_channel_client_test_remote_cap(playback_channel->base.channel_client, + SPICE_PLAYBACK_CAP_CELT_0_5_1); + int client_can_opus = red_channel_client_test_remote_cap(playback_channel->base.channel_client, + SPICE_PLAYBACK_CAP_OPUS); + int desired_mode = snd_desired_audio_mode(st->frequency, client_can_celt, client_can_opus); + playback_channel->mode = SPICE_AUDIO_DATA_MODE_RAW; + if (desired_mode != SPICE_AUDIO_DATA_MODE_RAW) { + if (snd_codec_create(&playback_channel->codec, desired_mode, st->frequency, SND_CODEC_ENCODE) == SND_CODEC_OK) { + playback_channel->mode = desired_mode; + } else { + spice_printerr("create encoder failed"); + } + } + + if (!red_client_during_migrate_at_target(client)) { + on_new_playback_channel(worker); + } + + if (worker->active) { + spice_server_playback_start(st->sin); + } + snd_playback_send(worker->connection); +} + +static void snd_record_migrate_channel_client(RedChannelClient *rcc) +{ + SndWorker *worker; + + spice_debug(NULL); + spice_assert(rcc->channel); + spice_assert(rcc->channel->data); + worker = (SndWorker *)rcc->channel->data; + + if (worker->connection) { + spice_assert(worker->connection->channel_client == rcc); + snd_set_command(worker->connection, SND_RECORD_MIGRATE_MASK); + snd_record_send(worker->connection); + } +} + +SPICE_GNUC_VISIBLE void spice_server_record_set_volume(SpiceRecordInstance *sin, + uint8_t nchannels, + uint16_t *volume) +{ + SpiceVolumeState *st = &sin->st->volume; + SndChannel *channel = sin->st->worker.connection; + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); + + st->volume_nchannels = nchannels; + free(st->volume); + st->volume = spice_memdup(volume, sizeof(uint16_t) * nchannels); + + if (!channel || nchannels == 0) + return; + + snd_record_send_volume(record_channel); +} + +SPICE_GNUC_VISIBLE void spice_server_record_set_mute(SpiceRecordInstance *sin, uint8_t mute) +{ + SpiceVolumeState *st = &sin->st->volume; + SndChannel *channel = sin->st->worker.connection; + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); + + st->mute = mute; + + if (!channel) + return; + + snd_record_send_mute(record_channel); +} + +SPICE_GNUC_VISIBLE void spice_server_record_start(SpiceRecordInstance *sin) +{ + SndChannel *channel = sin->st->worker.connection; + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); + + sin->st->worker.active = 1; + if (!channel) + return; + spice_assert(!record_channel->base.active); + record_channel->base.active = TRUE; + record_channel->read_pos = record_channel->write_pos = 0; //todo: improve by + //stream generation + if (!record_channel->base.client_active) { + snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK); + snd_record_send(&record_channel->base); + } else { + record_channel->base.command &= ~SND_RECORD_CTRL_MASK; + } +} + +SPICE_GNUC_VISIBLE void spice_server_record_stop(SpiceRecordInstance *sin) +{ + SndChannel *channel = sin->st->worker.connection; + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); + + sin->st->worker.active = 0; + if (!channel) + return; + spice_assert(record_channel->base.active); + record_channel->base.active = FALSE; + if (record_channel->base.client_active) { + snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK); + snd_record_send(&record_channel->base); + } else { + record_channel->base.command &= ~SND_RECORD_CTRL_MASK; + } +} + +SPICE_GNUC_VISIBLE uint32_t spice_server_record_get_samples(SpiceRecordInstance *sin, + uint32_t *samples, uint32_t bufsize) +{ + SndChannel *channel = sin->st->worker.connection; + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); + uint32_t read_pos; + uint32_t now; + uint32_t len; + + if (!channel) + return 0; + spice_assert(record_channel->base.active); + + if (record_channel->write_pos < RECORD_SAMPLES_SIZE / 2) { + return 0; + } + + len = MIN(record_channel->write_pos - record_channel->read_pos, bufsize); + + if (len < bufsize) { + SndWorker *worker = record_channel->base.worker; + snd_receive(record_channel); + if (!worker->connection) { + return 0; + } + len = MIN(record_channel->write_pos - record_channel->read_pos, bufsize); + } + + read_pos = record_channel->read_pos % RECORD_SAMPLES_SIZE; + record_channel->read_pos += len; + now = MIN(len, RECORD_SAMPLES_SIZE - read_pos); + memcpy(samples, &record_channel->samples[read_pos], now * 4); + if (now < len) { + memcpy(samples + now, record_channel->samples, (len - now) * 4); + } + return len; +} + +SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_playback_rate(SpicePlaybackInstance *sin) +{ + int client_can_opus = TRUE; + if (sin && sin->st->worker.connection) { + SndChannel *channel = sin->st->worker.connection; + PlaybackChannel *playback_channel = SPICE_CONTAINEROF(channel, PlaybackChannel, base); + client_can_opus = red_channel_client_test_remote_cap(playback_channel->base.channel_client, + SPICE_PLAYBACK_CAP_OPUS); + } + + if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY)) + return SND_CODEC_OPUS_PLAYBACK_FREQ; + + return SND_CODEC_CELT_PLAYBACK_FREQ; +} + +SPICE_GNUC_VISIBLE void spice_server_set_playback_rate(SpicePlaybackInstance *sin, uint32_t frequency) +{ + RedChannel *channel = sin->st->worker.base_channel; + sin->st->frequency = frequency; + if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency)) + red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_OPUS); +} + +SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_record_rate(SpiceRecordInstance *sin) +{ + int client_can_opus = TRUE; + if (sin && sin->st->worker.connection) { + SndChannel *channel = sin->st->worker.connection; + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); + client_can_opus = red_channel_client_test_remote_cap(record_channel->base.channel_client, + SPICE_RECORD_CAP_OPUS); + } + + if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, SND_CODEC_ANY_FREQUENCY)) + return SND_CODEC_OPUS_PLAYBACK_FREQ; + + return SND_CODEC_CELT_PLAYBACK_FREQ; +} + +SPICE_GNUC_VISIBLE void spice_server_set_record_rate(SpiceRecordInstance *sin, uint32_t frequency) +{ + RedChannel *channel = sin->st->worker.base_channel; + sin->st->frequency = frequency; + if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency)) + red_channel_set_cap(channel, SPICE_RECORD_CAP_OPUS); +} + +static void on_new_record_channel(SndWorker *worker) +{ + RecordChannel *record_channel = (RecordChannel *)worker->connection; + SpiceRecordState *st = SPICE_CONTAINEROF(worker, SpiceRecordState, worker); + + spice_assert(record_channel); + + if (st->volume.volume_nchannels) { + snd_set_command((SndChannel *)record_channel, SND_RECORD_VOLUME_MASK); + } + if (record_channel->base.active) { + snd_set_command((SndChannel *)record_channel, SND_RECORD_CTRL_MASK); + } +} + +static void snd_record_cleanup(SndChannel *channel) +{ + RecordChannel *record_channel = SPICE_CONTAINEROF(channel, RecordChannel, base); + snd_codec_destroy(&record_channel->codec); +} + +static void snd_set_record_peer(RedChannel *channel, RedClient *client, RedsStream *stream, + int migration, int num_common_caps, uint32_t *common_caps, + int num_caps, uint32_t *caps) +{ + SndWorker *worker = channel->data; + RecordChannel *record_channel; + SpiceRecordState *st = SPICE_CONTAINEROF(worker, SpiceRecordState, worker); + + snd_disconnect_channel(worker->connection); + + if (!(record_channel = (RecordChannel *)__new_channel(worker, + sizeof(*record_channel), + SPICE_CHANNEL_RECORD, + client, + stream, + migration, + snd_record_send, + snd_record_handle_message, + snd_record_on_message_done, + snd_record_cleanup, + common_caps, num_common_caps, + caps, num_caps))) { + return; + } + + record_channel->mode = SPICE_AUDIO_DATA_MODE_RAW; + + worker->connection = &record_channel->base; + + on_new_record_channel(worker); + if (worker->active) { + spice_server_record_start(st->sin); + } + snd_record_send(worker->connection); +} + +static void snd_playback_migrate_channel_client(RedChannelClient *rcc) +{ + SndWorker *worker; + + spice_assert(rcc->channel); + spice_assert(rcc->channel->data); + worker = (SndWorker *)rcc->channel->data; + spice_debug(NULL); + + if (worker->connection) { + spice_assert(worker->connection->channel_client == rcc); + snd_set_command(worker->connection, SND_PLAYBACK_MIGRATE_MASK); + snd_playback_send(worker->connection); + } +} + +static void add_worker(SndWorker *worker) +{ + worker->next = workers; + workers = worker; +} + +static void remove_worker(SndWorker *worker) +{ + SndWorker **now = &workers; + while (*now) { + if (*now == worker) { + *now = worker->next; + return; + } + now = &(*now)->next; + } + spice_printerr("not found"); +} + +void snd_attach_playback(SpicePlaybackInstance *sin) +{ + SndWorker *playback_worker; + RedChannel *channel; + ClientCbs client_cbs = { NULL, }; + + sin->st = spice_new0(SpicePlaybackState, 1); + sin->st->sin = sin; + playback_worker = &sin->st->worker; + sin->st->frequency = SND_CODEC_CELT_PLAYBACK_FREQ; /* Default to the legacy rate */ + + // TODO: Make RedChannel base of worker? instead of assigning it to channel->data + channel = red_channel_create_dummy(sizeof(RedChannel), SPICE_CHANNEL_PLAYBACK, 0); + + channel->data = playback_worker; + client_cbs.connect = snd_set_playback_peer; + client_cbs.disconnect = snd_disconnect_channel_client; + client_cbs.migrate = snd_playback_migrate_channel_client; + red_channel_register_client_cbs(channel, &client_cbs); + red_channel_set_data(channel, playback_worker); + + if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY)) + red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_CELT_0_5_1); + + red_channel_set_cap(channel, SPICE_PLAYBACK_CAP_VOLUME); + + playback_worker->base_channel = channel; + add_worker(playback_worker); + reds_register_channel(playback_worker->base_channel); +} + +void snd_attach_record(SpiceRecordInstance *sin) +{ + SndWorker *record_worker; + RedChannel *channel; + ClientCbs client_cbs = { NULL, }; + + sin->st = spice_new0(SpiceRecordState, 1); + sin->st->sin = sin; + record_worker = &sin->st->worker; + sin->st->frequency = SND_CODEC_CELT_PLAYBACK_FREQ; /* Default to the legacy rate */ + + // TODO: Make RedChannel base of worker? instead of assigning it to channel->data + channel = red_channel_create_dummy(sizeof(RedChannel), SPICE_CHANNEL_RECORD, 0); + + channel->data = record_worker; + client_cbs.connect = snd_set_record_peer; + client_cbs.disconnect = snd_disconnect_channel_client; + client_cbs.migrate = snd_record_migrate_channel_client; + red_channel_register_client_cbs(channel, &client_cbs); + red_channel_set_data(channel, record_worker); + if (snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_CELT_0_5_1, SND_CODEC_ANY_FREQUENCY)) + red_channel_set_cap(channel, SPICE_RECORD_CAP_CELT_0_5_1); + red_channel_set_cap(channel, SPICE_RECORD_CAP_VOLUME); + + record_worker->base_channel = channel; + add_worker(record_worker); + reds_register_channel(record_worker->base_channel); +} + +static void snd_detach_common(SndWorker *worker) +{ + if (!worker) { + return; + } + remove_worker(worker); + snd_disconnect_channel(worker->connection); + reds_unregister_channel(worker->base_channel); + red_channel_destroy(worker->base_channel); +} + +static void spice_playback_state_free(SpicePlaybackState *st) +{ + free(st->volume.volume); + free(st); +} + +void snd_detach_playback(SpicePlaybackInstance *sin) +{ + snd_detach_common(&sin->st->worker); + spice_playback_state_free(sin->st); +} + +static void spice_record_state_free(SpiceRecordState *st) +{ + free(st->volume.volume); + free(st); +} + +void snd_detach_record(SpiceRecordInstance *sin) +{ + snd_detach_common(&sin->st->worker); + spice_record_state_free(sin->st); +} + +void snd_set_playback_compression(int on) +{ + SndWorker *now = workers; + + playback_compression = !!on; + + for (; now; now = now->next) { + if (now->base_channel->type == SPICE_CHANNEL_PLAYBACK && now->connection) { + PlaybackChannel* playback = (PlaybackChannel*)now->connection; + SpicePlaybackState *st = SPICE_CONTAINEROF(now, SpicePlaybackState, worker); + int client_can_celt = red_channel_client_test_remote_cap(playback->base.channel_client, + SPICE_PLAYBACK_CAP_CELT_0_5_1); + int client_can_opus = red_channel_client_test_remote_cap(playback->base.channel_client, + SPICE_PLAYBACK_CAP_OPUS); + int desired_mode = snd_desired_audio_mode(st->frequency, client_can_opus, client_can_celt); + if (playback->mode != desired_mode) { + playback->mode = desired_mode; + snd_set_command(now->connection, SND_PLAYBACK_MODE_MASK); + } + } + } +} diff --git a/server/sound.h b/server/sound.h new file mode 100644 index 0000000..97f8410 --- /dev/null +++ b/server/sound.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SOUND_H_ +#define SOUND_H_ + +#include "spice.h" + +void snd_attach_playback(SpicePlaybackInstance *sin); +void snd_detach_playback(SpicePlaybackInstance *sin); + +void snd_attach_record(SpiceRecordInstance *sin); +void snd_detach_record(SpiceRecordInstance *sin); + +void snd_set_playback_compression(int on); + +void snd_set_playback_latency(RedClient *client, uint32_t latency); + +#endif diff --git a/server/spice_image_cache.c b/server/spice_image_cache.c deleted file mode 100644 index 1c5de24..0000000 --- a/server/spice_image_cache.c +++ /dev/null @@ -1,214 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009-2015 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include "spice_image_cache.h" -#include "red_parse_qxl.h" -#include "display-channel.h" - -static ImageCacheItem *image_cache_find(ImageCache *cache, uint64_t id) -{ - ImageCacheItem *item = cache->hash_table[id % IMAGE_CACHE_HASH_SIZE]; - - while (item) { - if (item->id == id) { - return item; - } - item = item->next; - } - return NULL; -} - -int image_cache_hit(ImageCache *cache, uint64_t id) -{ - ImageCacheItem *item; - if (!(item = image_cache_find(cache, id))) { - return FALSE; - } -#ifdef IMAGE_CACHE_AGE - item->age = cache->age; -#endif - ring_remove(&item->lru_link); - ring_add(&cache->lru, &item->lru_link); - return TRUE; -} - -static void image_cache_remove(ImageCache *cache, ImageCacheItem *item) -{ - ImageCacheItem **now; - - now = &cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE]; - for (;;) { - spice_assert(*now); - if (*now == item) { - *now = item->next; - break; - } - now = &(*now)->next; - } - ring_remove(&item->lru_link); - pixman_image_unref(item->image); - free(item); -#ifndef IMAGE_CACHE_AGE - cache->num_items--; -#endif -} - -#define IMAGE_CACHE_MAX_ITEMS 2 - -static void image_cache_put(SpiceImageCache *spice_cache, uint64_t id, pixman_image_t *image) -{ - ImageCache *cache = (ImageCache *)spice_cache; - ImageCacheItem *item; - -#ifndef IMAGE_CACHE_AGE - if (cache->num_items == IMAGE_CACHE_MAX_ITEMS) { - ImageCacheItem *tail = (ImageCacheItem *)ring_get_tail(&cache->lru); - spice_assert(tail); - image_cache_remove(cache, tail); - } -#endif - - item = spice_new(ImageCacheItem, 1); - item->id = id; -#ifdef IMAGE_CACHE_AGE - item->age = cache->age; -#else - cache->num_items++; -#endif - item->image = pixman_image_ref(image); - ring_item_init(&item->lru_link); - - item->next = cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE]; - cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE] = item; - - ring_add(&cache->lru, &item->lru_link); -} - -static pixman_image_t *image_cache_get(SpiceImageCache *spice_cache, uint64_t id) -{ - ImageCache *cache = (ImageCache *)spice_cache; - - ImageCacheItem *item = image_cache_find(cache, id); - if (!item) { - spice_error("not found"); - } - return pixman_image_ref(item->image); -} - -void image_cache_init(ImageCache *cache) -{ - static SpiceImageCacheOps image_cache_ops = { - image_cache_put, - image_cache_get, - }; - - cache->base.ops = &image_cache_ops; - memset(cache->hash_table, 0, sizeof(cache->hash_table)); - ring_init(&cache->lru); -#ifdef IMAGE_CACHE_AGE - cache->age = 0; -#else - cache->num_items = 0; -#endif -} - -void image_cache_reset(ImageCache *cache) -{ - ImageCacheItem *item; - - while ((item = (ImageCacheItem *)ring_get_head(&cache->lru))) { - image_cache_remove(cache, item); - } -#ifdef IMAGE_CACHE_AGE - cache->age = 0; -#endif -} - -#define IMAGE_CACHE_DEPTH 4 - -void image_cache_aging(ImageCache *cache) -{ -#ifdef IMAGE_CACHE_AGE - ImageCacheItem *item; - - cache->age++; - while ((item = (ImageCacheItem *)ring_get_tail(&cache->lru)) && - cache->age - item->age > IMAGE_CACHE_DEPTH) { - image_cache_remove(cache, item); - } -#endif -} - -void image_cache_localize(ImageCache *cache, SpiceImage **image_ptr, - SpiceImage *image_store, Drawable *drawable) -{ - SpiceImage *image = *image_ptr; - - if (image == NULL) { - spice_assert(drawable != NULL); - spice_assert(drawable->red_drawable->self_bitmap_image != NULL); - *image_ptr = drawable->red_drawable->self_bitmap_image; - return; - } - - if (image_cache_hit(cache, image->descriptor.id)) { - image_store->descriptor = image->descriptor; - image_store->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE; - image_store->descriptor.flags = 0; - *image_ptr = image_store; - return; - } - - switch (image->descriptor.type) { - case SPICE_IMAGE_TYPE_QUIC: { - image_store->descriptor = image->descriptor; - image_store->u.quic = image->u.quic; - *image_ptr = image_store; -#ifdef IMAGE_CACHE_AGE - image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME; -#else - if (image_store->descriptor.width * image->descriptor.height >= 640 * 480) { - image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME; - } -#endif - break; - } - case SPICE_IMAGE_TYPE_BITMAP: - case SPICE_IMAGE_TYPE_SURFACE: - /* nothing */ - break; - default: - spice_error("invalid image type"); - } -} - -void image_cache_localize_brush(ImageCache *cache, SpiceBrush *brush, SpiceImage *image_store) -{ - if (brush->type == SPICE_BRUSH_TYPE_PATTERN) { - image_cache_localize(cache, &brush->u.pattern.pat, image_store, NULL); - } -} - -void image_cache_localize_mask(ImageCache *cache, SpiceQMask *mask, SpiceImage *image_store) -{ - if (mask->bitmap) { - image_cache_localize(cache, &mask->bitmap, image_store, NULL); - } -} diff --git a/server/spice_image_cache.h b/server/spice_image_cache.h deleted file mode 100644 index 6d6b32d..0000000 --- a/server/spice_image_cache.h +++ /dev/null @@ -1,65 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009-2015 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifndef H_SPICE_IMAGE_CACHE -#define H_SPICE_IMAGE_CACHE - -#include <inttypes.h> - -#include "common/pixman_utils.h" -#include "common/canvas_base.h" -#include "common/ring.h" - -/* FIXME: move back to display_channel.h (once structs are private) */ -typedef struct Drawable Drawable; -typedef struct DisplayChannelClient DisplayChannelClient; - -typedef struct ImageCacheItem { - RingItem lru_link; - uint64_t id; -#ifdef IMAGE_CACHE_AGE - uint32_t age; -#endif - struct ImageCacheItem *next; - pixman_image_t *image; -} ImageCacheItem; - -#define IMAGE_CACHE_HASH_SIZE 1024 - -typedef struct ImageCache { - SpiceImageCache base; - ImageCacheItem *hash_table[IMAGE_CACHE_HASH_SIZE]; - Ring lru; -#ifdef IMAGE_CACHE_AGE - uint32_t age; -#else - uint32_t num_items; -#endif -} ImageCache; - -int image_cache_hit (ImageCache *cache, uint64_t id); -void image_cache_init (ImageCache *cache); -void image_cache_reset (ImageCache *cache); -void image_cache_aging (ImageCache *cache); -void image_cache_localize (ImageCache *cache, SpiceImage **image_ptr, - SpiceImage *image_store, Drawable *drawable); -void image_cache_localize_brush (ImageCache *cache, SpiceBrush *brush, - SpiceImage *image_store); -void image_cache_localize_mask (ImageCache *cache, SpiceQMask *mask, - SpiceImage *image_store); - -#endif diff --git a/server/spicevmc.c b/server/spicevmc.c index d37b1ec..52a29a4 100644 --- a/server/spicevmc.c +++ b/server/spicevmc.c @@ -30,10 +30,10 @@ #include "common/generated_server_marshallers.h" -#include "char_device.h" +#include "char-device.h" #include "red_channel.h" #include "reds.h" -#include "migration_protocol.h" +#include "migration-protocol.h" /* todo: add flow control. i.e., * (a) limit the tokens available for the client diff --git a/server/stream.h b/server/stream.h index 214d1df..cb2b844 100644 --- a/server/stream.h +++ b/server/stream.h @@ -20,10 +20,10 @@ #include <glib.h> #include "utils.h" -#include "mjpeg_encoder.h" +#include "mjpeg-encoder.h" #include "common/region.h" #include "red_channel.h" -#include "spice_image_cache.h" +#include "image-cache.h" #define RED_STREAM_DETACTION_MAX_DELTA ((1000 * 1000 * 1000) / 5) // 1/5 sec #define RED_STREAM_CONTINUS_MAX_DELTA (1000 * 1000 * 1000) diff --git a/server/sw-canvas.c b/server/sw-canvas.c new file mode 100644 index 0000000..0ef050e --- /dev/null +++ b/server/sw-canvas.c @@ -0,0 +1,26 @@ +/* + Copyright (C) 2011 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "common/spice_common.h" + +#include "sw-canvas.h" +#define SW_CANVAS_IMAGE_CACHE +#include "common/sw_canvas.c" +#undef SW_CANVAS_IMAGE_CACHE diff --git a/server/sw-canvas.h b/server/sw-canvas.h new file mode 100644 index 0000000..5ffba3d --- /dev/null +++ b/server/sw-canvas.h @@ -0,0 +1,24 @@ +/* + Copyright (C) 2011 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef SW_CANVAS_H_ +#define SW_CANVAS_H_ + +#define SW_CANVAS_IMAGE_CACHE +#include "common/sw_canvas.h" +#undef SW_CANVAS_IMAGE_CACHE + +#endif diff --git a/server/zlib-encoder.c b/server/zlib-encoder.c new file mode 100644 index 0000000..069a448 --- /dev/null +++ b/server/zlib-encoder.c @@ -0,0 +1,125 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2010 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "red_common.h" +#include "zlib-encoder.h" +#include <zlib.h> + +struct ZlibEncoder { + ZlibEncoderUsrContext *usr; + + z_stream strm; + int last_level; +}; + +ZlibEncoder* zlib_encoder_create(ZlibEncoderUsrContext *usr, int level) +{ + ZlibEncoder *enc; + int z_ret; + + if (!usr->more_space || !usr->more_input) { + return NULL; + } + + enc = spice_new0(ZlibEncoder, 1); + + enc->usr = usr; + + enc->strm.zalloc = Z_NULL; + enc->strm.zfree = Z_NULL; + enc->strm.opaque = Z_NULL; + + z_ret = deflateInit(&enc->strm, level); + enc->last_level = level; + if (z_ret != Z_OK) { + spice_printerr("zlib error"); + free(enc); + return NULL; + } + + return enc; +} + +void zlib_encoder_destroy(ZlibEncoder *encoder) +{ + deflateEnd(&encoder->strm); + free(encoder); +} + +/* returns the total size of the encoded data */ +int zlib_encode(ZlibEncoder *zlib, int level, int input_size, + uint8_t *io_ptr, unsigned int num_io_bytes) +{ + int flush; + int enc_size = 0; + int out_size = 0; + int z_ret; + + z_ret = deflateReset(&zlib->strm); + + if (z_ret != Z_OK) { + spice_error("deflateReset failed"); + } + + zlib->strm.next_out = io_ptr; + zlib->strm.avail_out = num_io_bytes; + + if (level != zlib->last_level) { + if (zlib->strm.avail_out == 0) { + zlib->strm.avail_out = zlib->usr->more_space(zlib->usr, &zlib->strm.next_out); + if (zlib->strm.avail_out == 0) { + spice_error("not enough space"); + } + } + z_ret = deflateParams(&zlib->strm, level, Z_DEFAULT_STRATEGY); + if (z_ret != Z_OK) { + spice_error("deflateParams failed"); + } + zlib->last_level = level; + } + + + do { + zlib->strm.avail_in = zlib->usr->more_input(zlib->usr, &zlib->strm.next_in); + if (zlib->strm.avail_in <= 0) { + spice_error("more input failed"); + } + enc_size += zlib->strm.avail_in; + flush = (enc_size == input_size) ? Z_FINISH : Z_NO_FLUSH; + while (1) { + int deflate_size = zlib->strm.avail_out; + z_ret = deflate(&zlib->strm, flush); + spice_assert(z_ret != Z_STREAM_ERROR); + out_size += deflate_size - zlib->strm.avail_out; + if (zlib->strm.avail_out) { + break; + } + + zlib->strm.avail_out = zlib->usr->more_space(zlib->usr, &zlib->strm.next_out); + if (zlib->strm.avail_out == 0) { + spice_error("not enough space"); + } + } + } while (flush != Z_FINISH); + + spice_assert(z_ret == Z_STREAM_END); + return out_size; +} diff --git a/server/zlib-encoder.h b/server/zlib-encoder.h new file mode 100644 index 0000000..0620fc7 --- /dev/null +++ b/server/zlib-encoder.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef _H_ZLIB_ENCODER +#define _H_ZLIB_ENCODER + +typedef struct ZlibEncoder ZlibEncoder; +typedef struct ZlibEncoderUsrContext ZlibEncoderUsrContext; + +struct ZlibEncoderUsrContext { + int (*more_space)(ZlibEncoderUsrContext *usr, uint8_t **io_ptr); + int (*more_input)(ZlibEncoderUsrContext *usr, uint8_t **input); +}; + +ZlibEncoder* zlib_encoder_create(ZlibEncoderUsrContext *usr, int level); +void zlib_encoder_destroy(ZlibEncoder *encoder); + +/* returns the total size of the encoded data */ +int zlib_encode(ZlibEncoder *zlib, int level, int input_size, + uint8_t *io_ptr, unsigned int num_io_bytes); +#endif diff --git a/server/zlib_encoder.c b/server/zlib_encoder.c deleted file mode 100644 index a3d2aa6..0000000 --- a/server/zlib_encoder.c +++ /dev/null @@ -1,125 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2010 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include "red_common.h" -#include "zlib_encoder.h" -#include <zlib.h> - -struct ZlibEncoder { - ZlibEncoderUsrContext *usr; - - z_stream strm; - int last_level; -}; - -ZlibEncoder* zlib_encoder_create(ZlibEncoderUsrContext *usr, int level) -{ - ZlibEncoder *enc; - int z_ret; - - if (!usr->more_space || !usr->more_input) { - return NULL; - } - - enc = spice_new0(ZlibEncoder, 1); - - enc->usr = usr; - - enc->strm.zalloc = Z_NULL; - enc->strm.zfree = Z_NULL; - enc->strm.opaque = Z_NULL; - - z_ret = deflateInit(&enc->strm, level); - enc->last_level = level; - if (z_ret != Z_OK) { - spice_printerr("zlib error"); - free(enc); - return NULL; - } - - return enc; -} - -void zlib_encoder_destroy(ZlibEncoder *encoder) -{ - deflateEnd(&encoder->strm); - free(encoder); -} - -/* returns the total size of the encoded data */ -int zlib_encode(ZlibEncoder *zlib, int level, int input_size, - uint8_t *io_ptr, unsigned int num_io_bytes) -{ - int flush; - int enc_size = 0; - int out_size = 0; - int z_ret; - - z_ret = deflateReset(&zlib->strm); - - if (z_ret != Z_OK) { - spice_error("deflateReset failed"); - } - - zlib->strm.next_out = io_ptr; - zlib->strm.avail_out = num_io_bytes; - - if (level != zlib->last_level) { - if (zlib->strm.avail_out == 0) { - zlib->strm.avail_out = zlib->usr->more_space(zlib->usr, &zlib->strm.next_out); - if (zlib->strm.avail_out == 0) { - spice_error("not enough space"); - } - } - z_ret = deflateParams(&zlib->strm, level, Z_DEFAULT_STRATEGY); - if (z_ret != Z_OK) { - spice_error("deflateParams failed"); - } - zlib->last_level = level; - } - - - do { - zlib->strm.avail_in = zlib->usr->more_input(zlib->usr, &zlib->strm.next_in); - if (zlib->strm.avail_in <= 0) { - spice_error("more input failed"); - } - enc_size += zlib->strm.avail_in; - flush = (enc_size == input_size) ? Z_FINISH : Z_NO_FLUSH; - while (1) { - int deflate_size = zlib->strm.avail_out; - z_ret = deflate(&zlib->strm, flush); - spice_assert(z_ret != Z_STREAM_ERROR); - out_size += deflate_size - zlib->strm.avail_out; - if (zlib->strm.avail_out) { - break; - } - - zlib->strm.avail_out = zlib->usr->more_space(zlib->usr, &zlib->strm.next_out); - if (zlib->strm.avail_out == 0) { - spice_error("not enough space"); - } - } - } while (flush != Z_FINISH); - - spice_assert(z_ret == Z_STREAM_END); - return out_size; -} diff --git a/server/zlib_encoder.h b/server/zlib_encoder.h deleted file mode 100644 index 0620fc7..0000000 --- a/server/zlib_encoder.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright (C) 2009 Red Hat, Inc. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#ifndef _H_ZLIB_ENCODER -#define _H_ZLIB_ENCODER - -typedef struct ZlibEncoder ZlibEncoder; -typedef struct ZlibEncoderUsrContext ZlibEncoderUsrContext; - -struct ZlibEncoderUsrContext { - int (*more_space)(ZlibEncoderUsrContext *usr, uint8_t **io_ptr); - int (*more_input)(ZlibEncoderUsrContext *usr, uint8_t **input); -}; - -ZlibEncoder* zlib_encoder_create(ZlibEncoderUsrContext *usr, int level); -void zlib_encoder_destroy(ZlibEncoder *encoder); - -/* returns the total size of the encoded data */ -int zlib_encode(ZlibEncoder *zlib, int level, int input_size, - uint8_t *io_ptr, unsigned int num_io_bytes); -#endif -- 2.4.3 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel