On Wed, Dec 2, 2015 at 5:19 PM, Frediano Ziglio <fziglio@xxxxxxxxxx> wrote: > --- > 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 Acked-by: Fabiano Fidêncio <fidencio@xxxxxxxxxx> _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel